Closed Bug 481815 Opened 15 years ago Closed 12 years ago

Provide a Windows service to update applications without asking Administrator password

Categories

(Toolkit :: Application Update, enhancement, P1)

x86
Windows Vista
enhancement

Tracking

()

RESOLVED FIXED
mozilla12
Tracking Status
firefox-esr10 --- fixed

People

(Reporter: renelanz, Assigned: bbondy)

References

(Blocks 2 open bugs, )

Details

(Keywords: dev-doc-complete, Whiteboard: dev-doc-needed for nsIWindowsRegKey.idl)

Attachments

(6 files, 148 obsolete files)

3.97 KB, patch
ted
: review+
Details | Diff | Splinter Review
2.85 KB, patch
robert.strong.bugs
: review+
emorley
: checkin+
Details | Diff | Splinter Review
18.81 KB, patch
ted
: review+
robert.strong.bugs
: review+
emorley
: checkin+
Details | Diff | Splinter Review
112.56 KB, patch
Details | Diff | Splinter Review
12.12 KB, patch
robert.strong.bugs
: review+
Details | Diff | Splinter Review
276.21 KB, patch
bbondy
: review+
Details | Diff | Splinter Review
User-Agent:       Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7
Build Identifier: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7

As Vista becomes popular, quite a lot of internet cafes use an unprivileged user for customers to access the web. Therefore Firefox will not be updated unless the administrator logs in from time to time. As most internet cafes are not aware of the importance of regular updates, old Firefox versions will be in use for a long time.

I think it would be a good idea to include a windows service with sufficient privileges checking and installing Firefox updates regularly regardless what user is logged in.

Reproducible: Always
Component: General → Application Update
Product: Firefox → Toolkit
QA Contact: general → application.update
An User Account doesn't have the permissions to upgrade the files and displaying an alert will annoy users because they can't upgrade.
I second this proposal.

Please change the summary to “Provide a Windows service to update applications without asking Administrator password” or something like that.  The problem which this bug tries to address is not specific to Internet cafes.

Using a Windows service is a well-known technique to update programs without user intervention on Windows.  For example, at least on Windows Vista, a service named “Windows Update” is enabled by default and used in the automatic update feature.  Some of the programs provided by Google also install an apparently similar service named “Google Software Updater.”

I'd like to note that Bug 318855 is related to this bug because both try to address some of the problems arising in the update without privileges.  They are not duplicates.

@Matthias: That is why a Windows service is desirable.
Summary changed. As Windows provides automatic SW updates (if not disabled) for its core components (read IE), security critical bugs in IE will be automatically fixed. 
As Firefox is not a core component of Windows (yet ;), security fixes will not be automatically downloaded and applied if the user does not have sufficient privileges. Thus, Firefox could be one step behind.
Summary: No Windows Service to update Firefox in Internet Cafes → Provide a Windows service to update applications without asking Administrator password
Released under the Apache License 2.0. As I understand that is incompatible with the MPL.
Another issue I experienced that would be solved with this enhancement:
1) Admin-Role on XP installed Firefox 3.0.5
2) User-Role used Firefox 3.0.5, eventually an update to 3.0.6 was downloaded. User does not have privilege to install update.
3) Admin-Role updated to Firefox 3.0.8
4) User-Role: Every time (now updated) Firefox 3.0.8 was started, Firefox warned about an old update to 3.0.6 as it was still pending.

=> From my point of view I would expect old updates to be deleted or ignored on startup. I guess this behaviour should be filed as a new bug?

=> How to proceed with this bug? Who decides in general if a windows service is desired to automatically update Firefox? Otherwises this bug will get old...
(In reply to comment #6)
>...
> => From my point of view I would expect old updates to be deleted or ignored on
> startup. I guess this behaviour should be filed as a new bug?
That is fixed for the next Firefox release which is 3.5 by bug 485624 and bug 313057.

> => How to proceed with this bug? Who decides in general if a windows service is
> desired to automatically update Firefox? Otherwises this bug will get old...
I've had a couple of discussions this week about whether this bug should be implemented. There is at least one other option which would be to implement MSI's and use its update mechanism since it supports updating for non-admin users. This most likely won't get fixed for Firefox 3.5 or any of the 3.5.x releases but there is a decent chance it will be fixed in one form or another for the next major release after 3.5
- Thank you for the prompt answer. As a regular and enthusiastic Firefox user I understand if this bug cannot be implemented for 3.5 anymore. On the other hand I also understand if people get a bad impression if SW updates do not run smoothly. 
As with my sister, they cannot handle the problem mentioned in #6 and don't want to bother. In this situation they need assistance. I assume the next major release would probably be in, let's say, 15 months? How do I explain to my sister Firefox 3.5 got a new feature as such and such but her "problem" is not solved?

- A few thoughts I have:
  - Firefox grew steadily in popularity, which is definitively great. But popularity brings also user expectations such as easy and safe upgrade and security fix procedures. Can we afford to postpone this for annother 15 months?

  - As I mentioned earlier, many internet cafes/shops install their computers once with a kiosk solution. Firefox 3.5 or newer may not be installed for a long time after release as they do not renew their computers/kiosk sw. Can we afford to have a lot of internet cafes run very old Firefox releases that are never updated? Of course it is the responsability of this shops, but most of them (at least in South East Asia) are just not aware of updates and their (security) impacts.

I just returned from a 6 month trip in South East Asia and thus visited internet cafes often. I managed to even see them running Firefox 1.5/2.0! If this is the case and Firefox cannot be upgraded manually, I have to use IE... which, in turn, is not good for "spreading the Firefox word".

  - As this enhancement would not solve the kiosk problem immediately, it would definitely solve it for the XP- and Vista Admin-/User Role. Internet Cafes not using kiosk sw tend to install sw with Admin rights only (which is a good thing). And it would solve it for non-tech people like my sister.

What do you think about my concerns/thoughts?
Release dates for software products are typically very fluid and I won't comment on when I personally think the next release is.

This bug in one form or another has always existed in Firefox during which time the popularity has grown. As far as app update is concerned chances are the majority of users haven't had a problem with it to the point where they weren't able to update. The problem still should be fixed though the way it is fixed may be different than implementing a service.

I am less concerned about kiosk systems than general user experience since kiosks are typically locked down to prevent users from adding / modifying / updating software. We would also have to provide the ability for the admins of these kiosks to not allow users updating which will likely take the form as an option in the installer. Are there examples of software that allow users on kiosks to update vs. requiring the admin to update it?

What about Firefox 1.5/2.0 being installed forces someone to use IE? This statement doesn't make sense to me.

We are constrained by time and resources to implement this and it simply won't be fixed for Firefox 3.5.
1) The SW usually used was http://www.tinasoft.com/easycafe. It allows fine control what a user can do. It was setup in general to allow an user to do almost everything - even installing SW - but after logout the machine was rebooted to a reverted, previous, clean and virus free state.

2) The main argument about not using old and maybe unmaintained FF is about not patched security issues (gaining admin-privileges, stealing private data...). Web standard support is also much better in FF 3 thus using older FF versions causes sometimes display problems. If I find an old FF and an IE 7, my choice is pretty clear - I prefer the more or less patched but more current browser.
Blocks: 627591
Status: UNCONFIRMED → NEW
Ever confirmed: true
Assignee: nobody → tglek
Is this bug just about Firefox?
(In reply to [Baboo] from comment #12)
> Is this bug just about Firefox?

The product is listed up top is "Toolkit", thus it could be used by any application using the Mozilla Toolkit. Firefox would of course be the first to do so, but this could also be applied to Thunderbird and SeaMonkey as well as any other application that uses the Mozilla Platform.
Assignee: tglek → netzen
Just providing a work in progress patch, not close to done yet nor do I need any feedback yet. 

Done so far:
- Created service base code
- Created service cmd line for install and uninstall + wait for stop
- icon & requireAdministrator manfiest
- Created makefile / build process + only build on winnt platform
- Added service to nsis installer and uninstaller.
Just wanted to provide an intermediate status update here:
- Created service base code (in attached patch)
- Created service cmd line for install and uninstall + wait for stop (in attached patch)
- icon & requireAdministrator manifest (in attached patch)
- Created makefile / build process + only build on winnt platform (in attached patch)
- Added service to nsis installer and uninstaller.  (in attached patch)
- Added code for directory change watching and parsing out values of pending update meta data (not yet uploaded)
- Added code for launching processes in different sessions (not yet uploaded)
- Added code for obtaining linked administrative token of a token of a given session ID to launch as administrator of (not yet uploaded)

Pending: 
- Writing out the update meta data file instead of calling updater.exe directly on startup
- Refactoring and Testing
- Add code for first time updates installing the service (nsis script)
- Create process flow documentation so that I can get a security review
- Anything else I forgot
Attachment #561107 - Attachment is obsolete: true
Attachment #562959 - Flags: review?(robert.bugzilla)
Attached patch Patch 5 of 6: Build procss (obsolete) — Splinter Review
Attachment #562964 - Flags: review?(robert.bugzilla)
Attachment #562964 - Attachment is patch: true
Attachment #562965 - Attachment is patch: true
Above I attached the first proof of concept in the form of 6 patches.  There are still a few TODOs for some edge cases I need to do inline in the patches.  Also we are currently too permissive on who can install updates.  So I need to think of a way to fix that.  Also I still need to do some things like check hashes to ensure we're executing the right things.

The way it works is that there is a new Windows service installed:
Display name: Mozilla Application Updater
Service name: MozillaUpdater
File name: updater_svc.exe

The service watches a directory for requested 'work items'. A 'work item' is just a file that contains the info that is usually passed to updater.exe to perform an update.  

Instead of Firefox.exe launching updater.exe on Windows, it will instead write a 'work item' and then shuts down on startup as normal.  If the service is not started, disabled, does not exist, or any other error occurs we will fall back to launching updater.exe directly again.

The service immediately picks up the 'work item' and figures out the correct session, windows station, and desktop to run the update as.  The service gets the user token for this session and then gets its linked token if running on Vista (UAC elevated one).  The service then executes updater.exe with this elevated token.  This is where the magic happens and it bypasses UAC. 

Since I'm not touching the logic of actually applying the updates, I think it is a much lower chance of regressions from what it could be if I tried to do the updates myself inside the service.  I thought it was best to leverage the stable and tested code as much as possible.

The service doesn't need to know about the different installations you currently have since it is just delegates the command line parameters and working directory to updater.exe.
The one thing I'm worried about here is how easy is it for a malicious app running as an unprivileged user to put something in the right place and permanently harming Firefox or even gain admin privs through that?
> The one thing I'm worried about here is how easy is it for a malicious app running as an unprivileged user to put something in the right place 

Right this is mainly what I was talking about at the top of Comment 22.   I'd like to add a hash-check on updater.exe to ensure we're always executing the app which is our code only from the service.  Maybe even code to verify the app is signed by us before executing it (but I'm not sure if we sign aurora and other channels yet).

As for limited account users, I don't think we want to allow them to update though, so I think I need to build some other mechanism there to make sure they can't initiate an update. They will not have a linked elevated token though so I think it would fall back to the way it works currently (which we want to fix by asking for elevated permissions).
For my reference, code for checking signature of an exe:
http://msdn.microsoft.com/en-us/library/aa382384%28VS.85%29.aspx
To what extend will the Mozilla Updater Service now be a separate product?
What if in the future Thunderbird and Firefox both use this and then the user uninstalls one of them?
What about different versions? (e.g.: user first installs Firefox Beta then installs Thunderbird stable which ships with an older version of the updater service)
Will there be a possibility to uninstall or even install it independently from the Mozilla applications using it?
Re Baboo: Some of these are still open questions, but below I provided my thoughts.

> To what extend will the Mozilla Updater Service now be a separate product?

It is my understanding that it will be shipped directly inside the installers of each product.  We may have a checkbox for optional install.

> What if in the future Thunderbird and Firefox both use this and then the user uninstalls one of them?

It would stay alive until the last product/channel is alive or my preference would be that we would just have a separate installer/uninstaller that gets executed silent for it from each product.

> What about different versions? [channels]

We would always have at most one service installed.  Initial task were 1 per product (e.g. firefox vs thunderbird) but I see no reason currently to even have it distinct from product to product.  I think the service with the highest core version number would always win and it would have to be always backwards compatible.

> Will there be a possibility to uninstall or even install it independently from the Mozilla applications using it?

If it's not started, disabled or not installed it will fall back to the old way of updating.  So I think we should allow both of these things via some documentation.
Brian, how do you envision us being able to update the updater service? Presumably updater.exe will need to be taught some new tricks?
> how do you envision us being able to update the updater service? Presumably updater.exe will need to be taught some new tricks?

I envision the nsis installer which is executed on update stopping the service if it's newer than what is available, then replacing then starting the service.
Comment on attachment 562959 [details] [diff] [review]
Patch 1 of 6: Base Windows service code (works but still in progress)

Looks good... please remove the progress bar.
Attachment #562959 - Flags: review?(robert.bugzilla) → feedback+
(In reply to Robert Strong [:rstrong] (do not email) from comment #30)
> Comment on attachment 562959 [details] [diff] [review] [diff] [details] [review]
> Patch 1 of 6: Base Windows service code (works but still in progress)
> 
> Looks good... please remove the progress bar.
and ui, etc.
Comment on attachment 562961 [details] [diff] [review]
Patch 3 of 6: /toolkit/xre code launching service work items (works but still in progress)

Patch summary says still in progress but has a review request open, so commenting anyway

>+  // Init the update directory path and ensure it exists.
>+  // Example: C:\programData\Mozilla\Firefox\updates
>+  WCHAR programData[MAX_PATH + 1];
>+  HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, 
>+    SHGFP_TYPE_CURRENT, programData);
>+  if (FAILED(hr)) {
>+    return FALSE;
>+  }
>+  if (!::PathAppendW(programData, L"Mozilla")) {
>+    return FALSE;
>+  }
>+  ::CreateDirectoryW(programData, NULL);
>+  if (!::PathAppendW(programData, L"Firefox")) {
>+    return FALSE;

More applications than just Firefox exist here and should be accounted for, especially 3'rd party compilations of Firefox (that can't use the trademark).
Attachment #562961 - Flags: feedback-
I had a similar comment.

>+BOOL GetUpdateDirectoryPath(WCHAR *path) 
>+{
>+  HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, 
>+    SHGFP_TYPE_CURRENT, path);
>+  if (FAILED(hr)) {
>+    return FALSE;
>+  }
>+  if (!::PathAppendW(path, L"Mozilla")) {
>+    return FALSE;
>+  }
>+  ::CreateDirectoryW(path, NULL);
>+  if (!::PathAppendW(path, L"Firefox")) {
>+    return FALSE;
>+  }
>+  ::CreateDirectoryW(path, NULL);
>+
>+  ::CreateDirectoryW(path, NULL);
>+  if (!::PathAppendW(path, L"updates")) {
>+    return FALSE;
>+  }
>+  ::CreateDirectoryW(path, NULL);
>+  return TRUE;
>+}
This won't work for other apps. I'd go with passing this data instead perhaps using the same file used to pass the session id.
Thanks for the feedback thus far!

> This won't work for other apps. I'd go with passing this data instead perhaps using the same file used to pass the session id.

I'll just monitor the parent directory (is Mozilla/updates ok?) and there is no need to pass the data.  Changes to any subdirectory of that folder would be seen by the service.  I couldn't pass the data anyway because the service needs to know which directory to watch before it watches it.
That should be fine with bug 572162 also fixed though it would be possible to use a directory that is writeable by All Users such as ProgramData on Win7.
Come to think of it doing that isn't dependent on bug 572162
(In reply to Justin Wood (:Callek) from comment #32)
> especially 3'rd party compilations of Firefox (that can't use the trademark).

Speaking of them, will they be even be able to use this service if it's going to check for (hardcoded?) signatures?
At this time we are primarily prototyping and the end result will allow others to use the service.
Status: NEW → ASSIGNED
See Also: → 663055
Here is the list of things left to do that I'll be tackling this week:

- Make wiki page for the software update process flow.
- Ask to schedule a security review once the wiki page is up
- Limited user accounts should allow updates at all times, if the service is installed.
- Add NSPR logging throughout the base service code and main service logic
- Add NSPR logging into update process inside Firefox
- Make updater code have a separate silent uninstaller that shows up under add/remove programs.
- Make updater code have a checkbox option on install for whether to install it or not.
- If a limited user account installs without elevation, then don't install the service nor show the option in the installer.
- Add an about:config option (or use an existing one) for whether to use the service for updates or not.  
- Default the new about:config option to off, but turn it to on by default for Firefox in particular.
- Possibly add new UI in Firefox preferences for whether or not to use the service.
- Separate out the update and post update process, investigate doing the main update as the system account in session 0 and the post update process as the user's session using his token.
- Fix up directory we are monitoring to not be Firefox specific
- Don't always use the same name for the update meta files so that 2 different updates from 2 different channels or applications can happen at the same time.
- Investigate how to deal with x86 + x64 installs.  (Currently planned to use the newest version regardless of x86 or x64)
- Ensure that only one service gets installed at most.
- Move the service code under /toolkit/components/ as it may later be used for other things than just updating.
- Find a better more generalized name for the service, something like Mozilla Maintenance Service?
- Make updates stop the service, apply the update, and then restart the service so that the service itself can be replaced.  
- Add first time update install of the service
- Add code to only install/update the service if the version number is greater than the last installed version number for the service.
- Possibly add telemetry so we know I) who will be using the service and ii) who isn't using the service explicitly, and iii) who is trying to but falling back to the old way because of some error.
- Possibly add check to make sure we signed the updater.exe if Nightly builds get signed.
- Possibly do a new hash check on the MAR file which is being applied (will likely be done in the context of another bug ID)
- No longer copy the updater.exe outside of Program files to execute it, that way we don't need to do a hash check on the file since it (should be) installed in a location that requires elevation like Program Files.  Use different name in same directory before executing updater.exe from the service.
One additional thing to check for with the move to a service: a long standing annoyance** exists for Windows computers using Fast User Switch (multiple semi-simultaneous users).  The updater does not realize when there are multiple copies of Firefox running under different accounts.  After an update is downloaded, it prompts you to restart firefox, but since it attempts to overwrite still open files (e.g. DLLs), it fails.

The separate updater service should be able to overcome this problem.  Possibly by only launching the updater process when all the instances of Firefox are down and notifying the current user that other users are still running Firefox.

** I actually don't see this issue in bugzilla, I can do that tonight.
I believe that we only apply updates currently if we can lock firefox.exe (open without sharing access).  So this would ensure it's not already in use.  But we probably fail to apply in the case you mentioned and the service could help here.  Sure please post a bug for this as the service can shutdown all instances across all windows sessions.  We can plan in the context of that ticket what to do exactly.
A lot of people in comments of the blog post are asking for this, so adding to the list of things to do:

- Figure out how to allow the service to be started/ stopped from unelevated processes.
- Add ability to start the service only on demand before applying the update and then stop again right after.
Why a service? Run a task with the task scheduler. I use this to workaround the UAC prompts and from what I see google also uses this way to update chrome.
We have previously discussed windows scheduled tasks but decided to do a service. 

As far as I know there are 2 or 3 different APIs for Windows task scheduler for different Windows operatian elevated administrator process you need to enter the user's username and ng systems.  Also at least in some of those APIs to run as password.   We need a solution that will work on all operating systems Win2k and above consistently. A service seems to be the cleanest way to do that.  As per comment 42 the service will be very low overhead.  Unlike Google we usually install into Program Files which requires elevation to write to.
Sorry I think that last message got garbled from drag and drop. 

Here it is again (corrected):

We have previously discussed windows scheduled tasks but decided to do a service.

As far as I know there are 2 or 3 different APIs for Windows task scheduler, each for different Windows operating systems.  

Also at least in some of those APIs to run as an elevated administrator process you need to enter the user's username and password.   We need a solution that will work on all operating systems Win2k and above consistently. A service seems to be the cleanest way to do that.  As per comment 42 the service will be very low overhead.  Unlike Google we usually install into Program Files which requires elevation to write to.
(In reply to Brian R. Bondy [:bbondy] from comment #45)
> Unlike Google we usually
> install into Program Files which requires elevation to write to.
Google Updater can update applications installed under Program Files without elevation or password.
Chrome is installed under user's profile by default to avoid UAC dialog on installation (NOT update).
I would prefer a scheduled task (Vista+ at least) but if a service is the chosen route I'd like back the plan that it should be strictly demand-start. I believe you can do this by using SetServiceObjectSecurity to add the SERVICE_START permission for local authenticated users (DOMAIN_ALIAS_RID_USERS?) to the service's ACL. That will allow any local user to start the service, and the service can shut itself down when it completes the update.
The ACE work was done earlier today and yes it was done using SetServiceObjectSecurity. The process is described already in the wiki for this service and silent updates here: https://wiki.mozilla.org/Windows_Service_Silent_Update

But that wiki is still heavily in progress, it would be better to read it tomorrow when I'm done with edits.
(In reply to Brian R. Bondy [:bbondy] from comment #44)
> We have previously discussed windows scheduled tasks but decided to do a
> service. 

ok, thanks :)
I have one feature request, which is that as part of the checking-for-work-to-do, the updater also checks for further updates from whatever source Firefox uses (on a similar schedule so as not to DoS the updater web site); that way, we'll always have the most up-to-date Firefox, even if we haven't run Firefox lately.
(In reply to Joe Drew (:JOEDREW!) from comment #50)
That is bug 353804. The patch has a bug in it that I need to find time to investigate.
Sorry, I was not clear enough. My feature request is for a service that will always keep us up to date without needing to run Firefox, similar to what (IIUC) Chrome has.
A couple of things I don't want to tackle in this bug are the security attack vectors that come with having a service or task that has access to the network, how we allow a user to disable updating from within Firefox if there is a service that does the download (keep in mind that a single install can have multiple profiles and multiple users), and the warning when the update will disable add-ons.

So, Firefox product drivers would need to at the very least decide to do the following first:
1. remove the option from within Firefox to disable updates
2. remove the warning when an update will disable add-ons

These two issues are difficult enough to solve that I don't want to hold back the implementation as it stands today for them to be fixed or decided upon in order to have the service perform the download as well.
Blocks: 692638
All that sounds great. I also wanted to make sure we didn't accidentally make any design decisions that precluded background update from happening.
Blocks: 692887
Depends on: 509158
Depends on: 663055
Brian,

Without reading too much I just want to comment a requirement for Gecko-Dependant Apps here. We can't *fully* rely on code-signing for this, at least not for SeaMonkey.

I am happy however to rely on it by default, pref it, whatever though. I just don't want us baking in a way that prevents SeaMonkey users from [auto-]updating simply because SeaMonkey has no access to a code-signing machine at present.
I am ok with there being a way for SeaMonkey to opt out of the signing requirement as long as it isn't so easy that a malicious app could opt out Firefox. I have no idea if this would be simple to implement and I am fairly certain that a pref definitely wouldn't be acceptable since a malicious app in the user session could change the pref.
(In reply to Robert Strong [:rstrong] (do not email) from comment #56)
> I am ok with there being a way for SeaMonkey to opt out of the signing
> requirement as long as it isn't so easy that a malicious app could opt out
> Firefox. I have no idea if this would be simple to implement and I am fairly
> certain that a pref definitely wouldn't be acceptable since a malicious app
> in the user session could change the pref.

Just to elaborate (since you're not on IRC atm)

I'm of the mindset that a malicious app on users computer means we have already lost :-). And we could require the pref to be app-dir only, like some other prefs we use/have relating to this stuff. In-fact the pref for Update Server restriction doesn't even follow that (profile-level prefs are enough)

In the end, I think if app-dir pref is ok, we should be good. Since in win7/Vista the Program Files dir is restricted by default, and if a malicious program can write/modify there as is, it is just as easy for it to also modify Firefox directly.

This said, if its *easier* to do ifdefs in code, I can accept that, but my experience is the more divergent codepaths exist, the easier it is to break them, especially when they are not part of Gecko/Firefox proper.
(In reply to Justin Wood (:Callek) from comment #57)
> (In reply to Robert Strong [:rstrong] (do not email) from comment #56)
> > I am ok with there being a way for SeaMonkey to opt out of the signing
> > requirement as long as it isn't so easy that a malicious app could opt out
> > Firefox. I have no idea if this would be simple to implement and I am fairly
> > certain that a pref definitely wouldn't be acceptable since a malicious app
> > in the user session could change the pref.
> 
> Just to elaborate (since you're not on IRC atm)
:rs on irc... I got complaints about another :rsxxx on bugzilla causing multiple matches

> I'm of the mindset that a malicious app on users computer means we have
> already lost :-). And we could require the pref to be app-dir only, like
> some other prefs we use/have relating to this stuff. In-fact the pref for
> Update Server restriction doesn't even follow that (profile-level prefs are
> enough)
I agree when it comes to the data in question being under program files or in HKLM but not in relation to the profile.

If this were to be opt out I think it would be simplest and safest for it to be build time.
> If this were to be opt out I think it would be simplest and safest for it to be build time.

A compile time #define flag sounds like a great way to disable the requirement for the exe you are launching to check signing.
There is a security risk there though as we are bypassing UAC, so if this is disabled, an alternate check should also be made like a check on the file hash of updater.exe.

> I'm of the mindset that a malicious app on users computer means we have already lost 

If we executed a malicious app instead of our own exe I think we would be blamed here, since we were the one that didn't show the user a UAC prompt.
I'm really only OK with a flag to disable it if an alternate security fix is implemented at the same time.

> In the end, I think if app-dir pref is ok, we should be good. Since in win7/Vista the Program Files dir is restricted
> by default, and if a malicious program can write/modify there as is, it is just as easy for it to also modify Firefox directly.

I was originally justifying it the same way, but I don't think that is enough justification.
The problem here is that WE will install into a non restricted location if the user is a limited user account.
Also the user can specify a non restricted location.  The former is not the user's fault, the later might be, but would in the end be our fault for not warning them or for allowing the service to be installed.
(In reply to Brian R. Bondy [:bbondy] from comment #41)
> I believe that we only apply updates currently if we can lock firefox.exe
> (open without sharing access).  So this would ensure it's not already in
> use.  But we probably fail to apply in the case you mentioned and the
> service could help here.  Sure please post a bug for this as the service can
> shutdown all instances across all windows sessions.  We can plan in the
> context of that ticket what to do exactly.

Some people use Firefox Preloader applications to speed it working.  Would you not have a rule that if after a period of n hours that you have not been able to get exclusive access to the Firefox.EXE that on starting Firefox the user is advised that an update from a date of Y is still pending - so if at a kiosk, one could see how out of date Firefox was?

Also re the updater working as a service - unless you can 100% guarantee that only genuine Mozilla products will be actioned by the service, then corporates and most users who want security will disable the service
Would it be possible for the updater service to be responsible for downloading the updates, instead of the application?  If you're aware of an enhancement request for that, please let me know.
> Some people use Firefox Preloader applications to speed it working...

I think there is some kind of notification eventually already.  This code is not being changed in any way, it will work the same as it used to.

> Unless you can 100% guarantee that only genuine Mozilla products will be actioned by the service

I think we will be 100% guaranteeing.

> Would it be possible for the updater service to be responsible for downloading the updates

I think this would be desirable, but is probably out of scope for this first release.
Just wanted to sync up the todo list for this task. 

Remaining items are:
- Investigate how to deal with x86 + x64 installs.  (Currently planned to use the newest version regardless of x86 or x64)
- Separate out the update and post update process, investigate doing the main update as the system account in session 0 and the post update process as the user's session using his token.
- Limited user accounts should allow updates at all times, if the service is installed.
- If a limited user account installs without elevation, then don't install the service nor show the option in the installer.
- Possibly add new UI in Firefox preferences for whether or not to use the service.
- Version number: Need to check all 4 components NSVersionComparitor *maybe switch to it
- Add NSPR logging into update process inside Firefox (already added into the service itself)
- Support installing to a different directory?
- Add check to make sure we signed the updater.exe if Nightly builds get signed.
- Possibly do a new hash check on the MAR file which is being applied (will likely be done in the context of another bug ID)le since it (should be) installed in a location that requires elevation like Program Files.  Use different name in same directory before executing updater.exe from the service.
- Possibly add telemetry so we know I) who will be using the service and ii) who isn't using the service explicitly, and iii) who is trying to but falling back to the old way because of some error.

Recently done:
- Added a debug command line parameter so you can run the service exe normally when you want to debug.
- Removed progress and dialog resource from the service since those are unused and were copied over by accident from the updater.exe project rc file.
- Changed the service to be on demand only at creation time instead of auto start which it used to be 
- Added code so the service can be started by user processes (set the service to have user access DACL)
- Added code for reading version information of executables so that it can be used to see when the service needs to be updated
- Made new nsAutoHandle and nsAutoServiceHandle classes to cleanup code which may one day lead to handle leaks
- Made updates stop the service, apply the update  so that the service itself can be replaced. 
- Now load WTSQueryUser token dynamically so that it will work on Win2k 
- Got rid of several edge cases that had TODO comments in them
- Updated Firefox code to attempt to start the service before attempting to write the service command.
- Added NSPR logging throughout the base service code and main service logic
- Added a new bool preference (app.update.service) in firefox.js defaulted to true
  - Added code inside nsUpdateDriver.cpp to use this pref, if the pref doesn't exist it defaults to false.
  - If the pref is set to false it will not even attempt to use the service for updates
- Make uninstaller have a components page that you can select what to uninstall
- Made new component installer page with required main app component and optional maintenance checkbox.  
  - User can now pick to install the service or not.  
  - Only shows up if you go to Custom setup. 
  - Defaults to on.
- Fix up directory we are monitoring to not be Firefox specific
- Moved the service code under /toolkit/components/maintenanceservice as it may later be used for other things than just updating.
- Renamed everything to Mozilla Maintenance Service, and removed anything specific in filenames to updater
- Added install of the service when updating
This is just a backup of the work for this task, I will split the patch up into sections again once it is ready for review.
Attachment #562959 - Attachment is obsolete: true
Attachment #562960 - Attachment is obsolete: true
Attachment #562961 - Attachment is obsolete: true
Attachment #562962 - Attachment is obsolete: true
Attachment #562964 - Attachment is obsolete: true
Attachment #562965 - Attachment is obsolete: true
Attachment #562960 - Flags: review?(robert.bugzilla)
Attachment #562961 - Flags: review?(robert.bugzilla)
Attachment #562962 - Flags: review?(robert.bugzilla)
Attachment #562964 - Flags: review?(robert.bugzilla)
Attachment #562965 - Flags: review?(robert.bugzilla)
Whiteboard: [secr:imelven]
(In reply to Brian R. Bondy [:bbondy] from comment #63)
> Just wanted to sync up the todo list for this task. 
> 
> Remaining items are:

> - Add check to make sure we signed the updater.exe if Nightly builds get
> signed.
> - Possibly do a new hash check on the MAR file which is being applied (will
> likely be done in the context of another bug ID)le since it (should be)
> installed in a location that requires elevation like Program Files.  Use
> different name in same directory before executing updater.exe from the
> service.

Just to be clear, SeaMonkey will need _a_ way to still have updates run even though even our releases are currently unsigned. If that is to add a new build-time define, sure. I say this again simply because you mentioned the "hash" thing would be a requirement of that, but you want to split that out.
(In reply to Justin Wood (:Callek) from comment #65)
Updates will still fallback to the current method if we aren't able to come up with a decent method for preventing this attack vector without using signing so SeaMonkey will still be able to update using the current method.
Attached image Ideas for optional install process (obsolete) —
We need to make the installer for Firefox have the ability to optionally install the service.  This UX review is to get feedback on the best way to do that.  This is only a temporary solution that will be shipped until the new installer stub is ready.

I proposed some ideas in the attached screenshot.  Red circles in the attachment indicate each place we could put the new option.  Also acceptable would be a whole new page.

Also I need feedback on the uninstall process.  Whether there should be a separate item in add/remove programs, a components whole installer step, or just a checkbox.
Attachment #566962 - Flags: ui-review?(faaborg)
Comment on attachment 566962 [details]
Ideas for optional install process

Let's just install the service, and then potentially allow users to stop it from somewhere in advanced preferences.  The problem with introducing it here is:

-increased complexity to the install process
-there is no way to undo the action later
Attachment #566962 - Flags: ui-review?(faaborg) → ui-review-
Alex, there has been a decent amount of flack type comments regarding not providing an opt out option in the installer and I would prefer not to alienate these people.

Also, this will be installed by the installer anyways which is what the installer is good at and the increased complexity is actually quite small for this because of this.

There will be a way to uninstall this via Add / Remove Programs and Programs and Features.

Adding an option in advanced preferences on the other hand requires Firefox to launch a separate binary to perform the task of disabling it. This is somewhat complicated.
Comment on attachment 566962 [details]
Ideas for optional install process

Alex, please reconsider after reading comment #69. I'm rather dead-set against the approach you suggested.
Attachment #566962 - Flags: ui-review- → ui-review?(faaborg)
>I would prefer not to alienate these people.

as would I, my premise being that establishing a pref inside of Firefox provides more control because they always have access to where the preference is (uninstalling the entire application by contrast isn't as much control).

>Adding an option in advanced preferences on the other hand requires Firefox to launch
>a separate binary to perform the task of disabling it. This is somewhat complicated.

If it is only somewhat complicated then I think it might be worth it as it provides more user control.  We could also add that level of control later in addition to a check box in the installer.
Comment on attachment 566962 [details]
Ideas for optional install process

let's go with just the third screen in (choose components, which is in the flow after they select custom install).
Attachment #566962 - Flags: ui-review?(faaborg) → ui-review+
(In reply to Alex Faaborg [:faaborg] (Firefox UX) from comment #71)
> >I would prefer not to alienate these people.
> 
> as would I, my premise being that establishing a pref inside of Firefox
> provides more control because they always have access to where the
> preference is (uninstalling the entire application by contrast isn't as much
> control).
I am ok with that but these people do not want the service installed at all. When the service is already installed whether it is from another Mozilla based application or a second installation of Firefox then the option would not be displayed.

The plan is for the service to only run on demand already and I am fine with having a pref that makes Firefox not use the service when it is installed.

> 
> >Adding an option in advanced preferences on the other hand requires Firefox to launch
> >a separate binary to perform the task of disabling it. This is somewhat complicated.
> 
> If it is only somewhat complicated then I think it might be worth it as it
> provides more user control.  We could also add that level of control later
> in addition to a check box in the installer.
See above. Having Firefox not use the service I am fine with.
Adding an option to the installer is pretty straightforward, but what about the first installation of the service via Firefox update?
> but what about the first installation of the service via Firefox update?

The plan is currently to install it via the update for existing users, and people can remove it if they don't want it.

> The plan is for the service to only run on demand already and I am fine with
> having a pref that makes Firefox not use the service when it is installed

I think that pref is independent of the instsall option. I'll put another proposed screenshot for that pref for UX review.
Rob are you OK with the components page on install and a separate uninstall (for the current temporary installer). If not how about a new custom page that just has a checkbox and its own step without the list box?
We definitely can't use that NSIS provided ui used in the screenshot unless it has had some fairly major rework done to it sometime in the last few years. We tried to when I implemented the installer for Firefox 2 and had to remove it since it isn't screen reader friendly.

Creating a custom page as is done for the shortcuts page would be a decent alternative. Might be a good thing to include some explanation about why it is a good thing not to uncheck the box on the page as well. Perhaps dynamically show it as is done for the profile removal in the uninstaller so it calls attention to itself if the user unchecks it.

Also, I don't think we want to show the page on install if the service is already installed.
OK I'll proceed with the new page similar to the component selection page but screen reader friendly.  (i.e. the same idea as the shortcut page). It'll have extra explanation and context inline on the page we can tweak later.

For the uninstaller it will show up as a separate product.

If you have any objections faaborg please let us know.

> Also, I don't think we want to show the page on install if the service is already installed.

Good catch, yup I agree.
Comment on attachment 567232 [details]
A proposed new option for whether or not the product should use the silent update service.  Only shows up if service is installed.

Brian, I recall you mentioning plans to bypass the UAC when manually applying an update if the service was present as well. If that is the case then it would make sense to align it with the radio buttons. Also, the ui changed recently in bug 600505.
Here is the certificate check code implemented.  It checks to make sure the information on the file we are executing's cert is correct and also checks to make sure it is trusted.

It is currently hard coded to the Mozilla info, but I have a TODO comment in there talking about moving that into HKLM registry.

This is a pretty early patch so I'm pretty sure it will have a security-review-minus for now but I would really appreciate any feedback and help on if I'm checking the certs correctly and if I'm susceptible to any sort of attacks.

When you look at the patch, it's easiest to read it if you start with:
> diff --git a/toolkit/components/maintenanceservice/workmonitor.cpp

Then follow the implementations of CheckCertificateForPEFile and  VerifyCertificateTrustForFile.

On a side note it might be worth also having this check from within Firefox and if it fails don't even try to use the service (fall back to the old method).  The check would be done as well in the service regardless.  If you agree it should be in Firefox as well, please advise on the best place in the tree to put the related PE file certificate check code (certificatecheck.cpp/.h).
Attachment #567241 - Flags: review?(imelven)
Attachment #567241 - Flags: feedback?(robert.bugzilla)
>  If that is the case then it would make sense to align it with the radio buttons

It doesn't do this now but that makes sense.  I'll align it with the radio buttons as you suggested. We can change it later if we want.
The r? for security is only looking for feedback, I just didn't have a second feedback? field to use.  So please only provide feedback for now then cancel the flag.
Comment on attachment 567241 [details] [diff] [review]
Patch for service certificate check on updater.exe and related logic

Didn't realize you could do comma separated fields for feedback/reviews, thx for the tip callek.
Attachment #567241 - Flags: review?(imelven) → feedback?(imelven)
Comment on attachment 567232 [details]
A proposed new option for whether or not the product should use the silent update service.  Only shows up if service is installed.

this is too detailed, let's leave off silent, which is controversial, UAC, which is jargon, and branding it as Mozilla Maintence, and just say:

"Use a background service to install the update"
Attachment #567232 - Flags: ui-review?(faaborg) → ui-review-
(In reply to Alex Faaborg [:faaborg] (Firefox UX) from comment #84)
> Comment on attachment 567232 [details]
> 
> "Use a background service to install the update"

bikeshed: "Use a background service to install updates"
Attachment #567667 - Flags: feedback?(robert.bugzilla)
Attachment #567669 - Flags: feedback?(robert.bugzilla)
Attachment #567669 - Attachment description: Screenshot: Showing up separately inside Add/Remve programs → Screenshot: Showing up separately inside Add/Remve programs (icon pending)
Attached is the new installer work redone.  

The installer process works by having a completely separate installer for the maintenance service.
You can run this standalone if you want with a GUI, but typically it will be run silently.
This new installer shows up in add/remove programs as Mozilla Maintenance Service.
 
The existing Firefox installer has a new property page the same as the shortcuts page but asking if you want to install the maintenance service.
The new NSIS maintenance service installer gets included and executed silently if the checkbox for Install Maintenance service is specified.

For feedback I'm looking for a general "yup you're in the right direction" or not. 
Also I'd like to know if there's a better way inside toolkit/mozapps/installer/windows/nsis/makensis.mk
from what I'm doing.

In particular in the new NSI I'm using File directives and to get those to work I have to copy the files I want into the installer dir makensis.mk:
$(INSTALL) $(addprefix $(shell pwd)/../../../dist/bin/,maintenanceservice.exe nspr4.dll) $(CONFIG_DIR)
+
I'd like to get rid of that line from makensis.mk for cleaner code.
Attachment #567671 - Flags: feedback?(robert.bugzilla)
Same as last patch w/ minor fix for default install service value and proper checking if it is checked.
Attachment #567671 - Attachment is obsolete: true
Attachment #567723 - Flags: feedback?(robert.bugzilla)
Attachment #567671 - Flags: feedback?(robert.bugzilla)
(In reply to Brian R. Bondy [:bbondy] from comment #64)
> Created attachment 566880 [details] [diff] [review] [diff] [details] [review]
> Intermediate backup of service related work
> 
> This is just a backup of the work for this task, I will split the patch up
> into sections again once it is ready for review.

i took a pass through this patch and the earlier iterations of it and sent bbondy a couple of small pieces of feedback. 

my primary findings security wise are that the service ACL allows access from the "Users" group (all authenticated users on the machine), which means any user can start, stop, etc. the service. If the service isn't running and can't be started the installer will fall back to the regular install with a UAC prompt. My chief concern is still the session ID spoofing mechanism, since any user can drop an update file with an arbitrary session ID into the 'pickup' directory, the post install actions can be run by an arbitrary user as an arbitrary user. This means it's crucial an attacker can't influence what actions are run or the parameters to these actions. Additionally, the service is running as SYSTEM so before landing, these needs another more thorough code review to look for input validation/overflow type problems.
Comment on attachment 567667 [details]
Screenshot: New installer step inside Firefox installer

Adding UX review for new installer step.
Pls + w/ nits if it is just text changes
Attachment #567667 - Flags: ui-review?(faaborg)
Proposed changes to the preferences dialog. 
This one is not a mockup but actually working.
Please ui-review+ w/ nits if just text changes.
Attachment #567667 - Attachment is obsolete: true
Attachment #567740 - Flags: ui-review?(faaborg)
Attachment #567740 - Flags: feedback?(robert.bugzilla)
Attachment #567667 - Flags: ui-review?(faaborg)
Attachment #567667 - Flags: feedback?(robert.bugzilla)
Attachment #567232 - Attachment is obsolete: true
Attachment #567232 - Flags: feedback?(robert.bugzilla)
Attachment #567740 - Attachment description: Screenshot: New installer step inside Firefox installer. v2 → A proposed new option for whether or not the product should use the silent update service. Only shows up if service is installed. v2
Attachment #567667 - Attachment is obsolete: false
Attachment #567667 - Flags: ui-review?(faaborg)
Attachment #567667 - Flags: feedback?(robert.bugzilla)
Attachment #566962 - Attachment is obsolete: true
Attachment #566962 - Flags: ui-review+
Comment on attachment 567241 [details] [diff] [review]
Patch for service certificate check on updater.exe and related logic

i took a pass through the patch, the windows-y/c++ part of it looks great.

from my reading, the code takes a hardcoded issuer, cert name, and signer info (program name, publisher and more info link), and uses this to look up a cert with matching details in the OS cert store, it then checks that the retrieved cert matches the hardcoded data it's looking for. if the cert specifies program and publisher info, this is also verified against the hardcoded set we are looking for. the code also verifies the signing certificate on the file chains to a root cert in the OS root store and that the certificate has the ability to sign code. it does not check for cert revocation. 

bbondy, can you elaborate on why in VerifyCertifcateTrustForFile an error TRUST_E_TIME_STAMP ends up saying the file is validated ?

also, your comment questioning the handling of CRYPT_E_SECURITY_SETTINGS is interesting. from searching it appears that AV programs and 3rd party firewalls can often result in this error - it appears that this error results from an admin not trusting the cert and prohibiting the user from trusting the cert - s my inclination is that this should NOT verify the file - however i'm wary of possible support issues if something messes up the cert store, BUT i also found postings saying that this error occurred when trying to update windows itself. so overall, my opinion is that we should fail the verification if this error occurs.

one more question, how does the signer cert initially get into the OS cert store ? additionally, what's the reasoning behind trying to find the match in the OS cert store and using that to compare against the hardcoded info rather than comparing the actual signing cert on the binary itself ? 

adding a feedback? to bsmith to take a look at the crypto specific code/functionality of the patch.
Attachment #567241 - Flags: feedback?(imelven)
Attachment #567241 - Flags: feedback?(bsmith)
Attachment #567241 - Flags: feedback+
Thanks for the feedback.

> from my reading...
 
Yup.

> bbondy, can you elaborate on why in VerifyCertifcateTrustForFile an error TRUST_E_TIME_STAMP ends up saying the file is validated ?

What I was concerned with here is an update that is installed after a certificate is expired, or a downgrade. If you think it is safest though I can return an error in that case and it would fall back to the normal update process without the service.

>  so overall, my opinion is that we should fail the verification if this error occurs.

OK I'll change that.  I'd rather only use this service update when we're sure it's safe in all cases. 

> one more question, how does the signer cert initially get into the OS cert store ?

I'm not sure about this, but my understadning is that there are several preloaded trusted authorities.  I have limited authenticode knowledge though.
This is also the first time I write any certificate checking code and I relied on several MSDN code samples that I pulled together.

> what's the reasoning behind trying to find the match in the OS cert store and using that to compare against the hardcoded info rather than comparing the actual signing cert on the binary itself

I think by doing it that way we have access to more information.
(In reply to Ian Melven :imelven from comment #94)
> from my reading, the code takes a hardcoded issuer, cert name, and signer
> info (program name, publisher and more info link)

Is there some mechanism to allow e.g. Firefox to switch the cert or issuer in case of expiring or the issuer becoming unused (we recently had a case where SeaMonkey's update service needed to switch issuers because the previous one has been phased out)?
> Is there some mechanism to allow e.g. Firefox to switch the cert or issuer in case of expiring or the issuer becoming unused

There's a new patch coming later today that will store the info for allowed certs in a trusted location in HKLM.  It's already done actually just needs testing. 

So yes we can update that location as we go, and add new certs for other prodcuts there as well.
(In reply to Brian R. Bondy [:bbondy] from comment #95)
>
> What I was concerned with here is an update that is installed after a
> certificate is expired, or a downgrade. If you think it is safest though I
> can return an error in that case and it would fall back to the normal update
> process without the service.

right, i think an expired cert should probably fail here. as far as downgrades, i'd be concerned about an arbitrary user on the machine forcing a downgrade, which seems bad. i'm not sure forcing the UAC path on a downgrade necessarily helps here though.

> I'm not sure about this, but my understadning is that there are several
> preloaded trusted authorities.  I have limited authenticode knowledge though.
> This is also the first time I write any certificate checking code and I
> relied on several MSDN code samples that I pulled together.

right, the root cert we use to validate the cert will be preloaded in the OS cert store but our signing cert most likely won't, i would think.
 
> I think by doing it that way we have access to more information.

that was my assumption, that we could get the extended fields like the url and such from the cert itself in the store but not necc. from the cert on the binary itself.
> Re:imelven cert changes:

Fixed both of these in favor of being more restrictive.
Why are certs involved at all as opposed to hard-coding the Mozilla code signing public key into the service?
(In reply to Henri Sivonen (:hsivonen) from comment #100)
> Why are certs involved at all as opposed to hard-coding the Mozilla code
> signing public key into the service?

one reason is that it gives us the ability to revoke or change the cert we use while not requiring a respin of the installers and allowing existing installers in the wild to continue functioning.
> Why are certs involved at all as opposed to hard-coding the Mozilla code signing public key into the service?

Thanks for the suggestion. 
I am not an expert in this area so I will leave divergence from the current Authenticode certificate sign checks patch for the security team to advise further on.
The install process is working and tested (just by me) now; works as follows:

Administrative users or Limited Users who are elevated when installing:
The optional service install page will show up unless the service is already installed.
If the service is not already installed the user can chose whether or not to install the service.
If the service is already installed the page will be skipped but the service will be upgraded.
If the user is attempting to install an older build when a newer service is already installed, the service will not be replaced. 

Limited users who are not elevated:
- The optional service page will NOT show up and the service will NOT be installed.
This is a backup of the code.  
The final code is still in progress and once done will be split up end of this week for review.

Ian there is some new code related to registry + authenticode checks.
The main code for this is in registrycertificates.cpp.
The other suggestions were updated inside of certificatecheck.cpp.
And then workmonitor.cpp was changed from hard coded info check to using CheckIfBinaryMatchesAllowedCertificates.

Rob if you are interested you can checkout the installer work early.
Attachment #566880 - Attachment is obsolete: true
Attachment #568174 - Flags: feedback?(imelven)
Attachment #567723 - Flags: feedback?(robert.bugzilla)
Attachment #567723 - Attachment is obsolete: true
Comment on attachment 567667 [details]
Screenshot: New installer step inside Firefox installer

Checkboxes are aligned with the text in the installer. Otherwise it looks good though faaborg still needs to approve the text. I personally think that Optional Recommended Components is a tad awkward.
Attachment #567667 - Flags: feedback?(robert.bugzilla) → feedback+
Comment on attachment 567740 [details]
A proposed new option for whether or not the product should use the silent update service. Only shows up if service is installed. v2

Please update this since the new UI has already landed on nightly. Also, make sure the checkbox looks a little to the left of where it should be.
Attachment #567740 - Flags: ui-review?(faaborg)
Attachment #567740 - Flags: feedback?(robert.bugzilla)
Attachment #567740 - Flags: feedback-
Comment on attachment 567668 [details]
Screenshot: Program files directory showing output files of Maintenance Service Installer

Though it is unlikely it is possible that another binary for a different app might be named maintenanceservice.exe. Might be a good thing to name it mozmaintenanceservice.exe or mozmaintsvc.exe.

What do you think?
Attachment #567668 - Flags: feedback?(robert.bugzilla) → feedback+
Comment on attachment 567669 [details]
Screenshot: Showing up separately inside Add/Remve programs (icon pending)

Looks good. I think it is best to not bother with putting the version in there like we do with Firefox since the only reason we do that with Firefox is so it is easy to differentiate side by side installs which won't be supported for the maintenance service.
Attachment #567669 - Flags: feedback?(robert.bugzilla) → feedback+
Comment on attachment 567241 [details] [diff] [review]
Patch for service certificate check on updater.exe and related logic

Instead of feedback reviewing this I am going to try to feedback review "Intermediate backup of service related work" - attachment #568174 [details] [diff] [review]
Attachment #567241 - Flags: feedback?(robert.bugzilla)
Attachment #567241 - Attachment is obsolete: true
Attachment #567241 - Flags: feedback?(bsmith)
Attachment #567241 - Flags: feedback+
Attachment #568174 - Flags: feedback?(robert.bugzilla)
Attachment #568174 - Flags: feedback?(bsmith)
Regarding the nsis service installer page feedback:

> Checkboxes are aligned with the text in the installer.

The alignment of the checkbox vs the text is currently consistent with the shortcuts page.

Please let me know your preference:
a) Align the service page checkbox more to the left with the above text, to be inconsistent with the shortcuts page?
b) Change the alignment on both the service page and the shortcuts page to be aligned with the text?
c) Leave as is in that screenshot?
Attachment #567740 - Attachment is obsolete: true
Attachment #568253 - Flags: ui-review?(faaborg)
Attachment #568253 - Flags: feedback?(robert.bugzilla)
Attachment #567668 - Attachment is obsolete: true
Comment on attachment 568253 [details]
Service checkbox; Preferences page option; v3.

Nice! In the past, we've put additional space between elements to denote the different functionality but I don't know if that is the case now so leaving that to faaborg.
Attachment #568253 - Flags: feedback?(robert.bugzilla) → feedback+
(In reply to Brian R. Bondy [:bbondy] from comment #110)
> Regarding the nsis service installer page feedback:
> 
> > Checkboxes are aligned with the text in the installer.
> 
> The alignment of the checkbox vs the text is currently consistent with the
> shortcuts page.
> 
> Please let me know your preference:
> a) Align the service page checkbox more to the left with the above text, to
> be inconsistent with the shortcuts page?
> b) Change the alignment on both the service page and the shortcuts page to
> be aligned with the text?
> c) Leave as is in that screenshot?
That's a bumer. :)

The main reason we went with aligning the checkbox with the installer is to allow more space for the locales that need it. I'll leave that to faaborg but I *think* I prefer going with aligning them and just fixing the shortcuts page.
Here's a better screenshot of the add/remove programs showing all fields and the icon.
Attachment #567669 - Attachment is obsolete: true
Attachment #568265 - Flags: ui-review?(faaborg)
Attachment #568265 - Flags: feedback?(robert.bugzilla)
Comment on attachment 568265 [details]
Showing up separately inside Add/Remve programs. v2.

Looks good!
Attachment #568265 - Flags: feedback?(robert.bugzilla) → feedback+
Comment on attachment 568174 [details] [diff] [review]
Intermediate backup of service related work

in DoesCertificateMatch, it seems a little strange that if no infoToMatch.issuer and infoToMatch.name are passed it, TRUE is returned. from the calling code it looks like this probably can't happen since we error if we can't get the signer info previously. 

the new code looks good to me, it goes through the allowed signing certs we have stored in the registry and returns TRUE on finding the first one that is successfully verified. The registry key used is under HKLM so non-Admin users should not have access to it by default.
Attachment #568174 - Flags: feedback?(imelven) → feedback+
Attachment #568174 - Attachment is obsolete: true
Attachment #568174 - Flags: feedback?(robert.bugzilla)
Attachment #568174 - Flags: feedback?(bsmith)
Attached patch Patch 1 - Base service code (obsolete) — Splinter Review
Attachment #568676 - Flags: review?(robert.bugzilla)
Attached patch Patch 2 - Main service logic (obsolete) — Splinter Review
Attachment #568677 - Flags: review?(robert.bugzilla)
Attachment #568678 - Flags: review?(robert.bugzilla)
Attachment #568679 - Flags: review?(robert.bugzilla)
Attachment #568680 - Flags: review?(robert.bugzilla)
Attachment #568682 - Flags: review?(robert.bugzilla)
Attached patch Patch 7 - RAII base helpers (obsolete) — Splinter Review
Attachment #568684 - Flags: review?(robert.bugzilla)
Attachment #568685 - Flags: review?(robert.bugzilla)
Attachment #568686 - Flags: review?(robert.bugzilla)
Attachment #568688 - Flags: review?(robert.bugzilla)
Attachment #568689 - Flags: review?(robert.bugzilla)
Attachment #568690 - Flags: review?(robert.bugzilla)
Attachment #568691 - Flags: review?(robert.bugzilla)
Attached patch Patch 14 - Build process (obsolete) — Splinter Review
Attachment #568693 - Flags: review?(robert.bugzilla)
All the patches thus far are uploaded.
The task isn't 100% complete, but it is working and ready for review while I finish up minor things in a couple follow up patches. 

I'm building a release config installer right now for testing as well which I'll upload later today.  It will include a signed updater.exe from FF7 so should work out of the box.
Some questions as part of the ui-review:

Does this windows service do anything other than update the application, do we have any plans for hang detection, or performance telemetry, or anything else? (trying to get a sense of if we should call it an update service since that is a more literal name).  Does it only update Firefox?

Where in the install wizard does "setup optional components" appear?  You can reference the numbers here: https://bug513414.bugzilla.mozilla.org/attachment.cgi?id=397421

I'm assuming we didn't group "use a background service to install updates" under automatic install because we might still use the service to apply manual installs?  At the very least we should perhaps put it under the button for update history so that it doesn't visually group as one of the top level options (3 radio buttons and a checkbox accidentally parses as 4 radio buttons)
is the service executable going to be signed also ?
> is the service executable going to be signed also ?

I think all EXE and DLL get signed, but I'm not sure if I need to add another bug specifically for the service.  Should I add a bug for releng for that Rob? (that's not important for me in nightly but just in general)
> Does this windows service do anything other than update the application, 
> do we have any plans for hang detection, or performance telemetry, or anything else?

Yes we have plans for other things, but for the initial release it will only be for silent updates without UAC prompts.

> Does it only update Firefox?

Initially it will only be launched for Firefox but it has support initially for other programs as well.  It will be used by more than Firefox.


> Where in the install wizard does "setup optional components" appear?  
> You can reference the numbers here: 
> https://bug513414.bugzilla.mozilla.org/attachment.cgi?id=397421

It would appear as a new step 5.5 just before the shortcuts page (only if the user selects custom).

> I'm assuming we didn't group "use a background service to install updates" 
> under automatic install because we might still use the service to apply manual installs?  

That is correct.

> At the very least we should perhaps put it under the button for update history so that it doesn't visually 
> group as one of the top level options (3 radio buttons and a checkbox accidentally parses as 4 radio buttons)

Sounds good, I was uncomfortable having it right under the radio buttons as well.
I'll move the checkbox under the update history button.
You can see a working demo on your computer here:
http://people.mozilla.com/~bbondy/InstallerWithService.zip

Instructions:
0. If UAC is off, turn UAC on with default value. Reboot.
1. Install the installer firefox-10.0a1.en-US.win32.installer.exe
2. Stage the update by extracting Nightly.zip into:
C:\Users\<username>\AppData\Local\Mozilla\Firefox
After extracting, your path should look like:
C:\Users\<username>\AppData\Local\Mozilla\Firefox\Nightly\updates\0
3. Do something like mark firefox.exe as hidden so you will know it got replaced.
4. Open Firefox to begin the update.

No UAC prompt should be shown. You should see a progress bar currently but that will probably be disabled later.

If this doesn't work, enable NSPR logging and get back to me with the log file.
For more info on NSPR logging, see the Logging section at: http://www.brianbondy.com/mozilla/cheatsheet/
The NSPR module name is: nsFirefoxService
Does not want to interrupt you developers, but isn't "Mozilla Update" better name than "Maintenance Service"?
Also Keep up the good work!
The service has the potential to do more than just update, such as other maintenance tasks.
Comment on attachment 567667 [details]
Screenshot: New installer step inside Firefox installer

Not ideal for us to add an extra step, but we'll be landing the stub installer at some point in the future.  so ui-review+ on the assumption that this is going to eventually get collapsed into a single options dialog in the stub installer.
Attachment #567667 - Flags: ui-review?(faaborg) → ui-review+
Comment on attachment 568253 [details]
Service checkbox; Preferences page option; v3.

looks good, just need to move down to below the update history button to break the  visual grouping (gestalt principal of proximity for the cog sci geeks playing along at home)
Attachment #568253 - Flags: ui-review?(faaborg) → ui-review+
Comment on attachment 568265 [details]
Showing up separately inside Add/Remve programs. v2.

This is fine for now, we're going to want to roll out a new icon for the service in the future, but we obviously don't need to block on that.
Attachment #568265 - Flags: ui-review?(faaborg) → ui-review+
Was already +'ed by faaborg so just marking as +.  Including the updated screenshot just in case there is further feedback.
Attachment #568253 - Attachment is obsolete: true
Attachment #568949 - Flags: ui-review+
Moved checkbox under update history button.
Attachment #568682 - Attachment is obsolete: true
Attachment #568950 - Flags: review?(robert.bugzilla)
Attachment #568682 - Flags: review?(robert.bugzilla)
Re: Ian:
> in DoesCertificateMatch, it seems a little strange that if no infoToMatch.issuer
> and infoToMatch.name are passed it, TRUE is returned. 
> from the calling code it looks like this probably can't happen since we
> error if we can't get the signer info previously

The reasoning was because you can specify any of those 5 criteria for people who want to add cert checking but sign in different ways than we do.  It was more recently updated to NULL or empty string returns TRUE.
re: rs
> Though it is unlikely it is possible that another binary for a different 
> app might be named maintenanceservice.exe. Might be a good thing to name 
> it mozmaintenanceservice.exe or mozmaintsvc.exe.

The same name wouldn't cause a problem for the exe name.
The svcname is MozillaMaintenance and the friendly name is Mozilla Maintenance Service.  Only the svcname could conflict with other services and not the exe name.  So I think we should just leave as is.  But if you prefer the moz prefix I can add that.
(In reply to Brian R. Bondy [:bbondy] from comment #145)
> Re: Ian:
> > in DoesCertificateMatch, it seems a little strange that if no infoToMatch.issuer
> > and infoToMatch.name are passed it, TRUE is returned. 
> > from the calling code it looks like this probably can't happen since we
> > error if we can't get the signer info previously
> 
> The reasoning was because you can specify any of those 5 criteria for people
> who want to add cert checking but sign in different ways than we do.  It was
> more recently updated to NULL or empty string returns TRUE.

makes sense, thanks for the explanation.
Attached patch Patch 2 - Main service logic. v2 (obsolete) — Splinter Review
- Wasn't stopping the service after the update, fixed.
- Now wait for return code of updater.exe before returning for better error handling and also better logging.
- Added compile time flag DISABLE_SERVICE_AUTHENTICODE_CHECK for when running on try tests
- Some code cleanup / refactoring
Attachment #568677 - Attachment is obsolete: true
Attachment #568993 - Flags: review?(robert.bugzilla)
Attachment #568677 - Flags: review?(robert.bugzilla)
Depends on: 696777
- Added new UACHelper class with new code for determining token types and user types
- Added handling for ensuring the token we're using is an elevated token
- Added handling for limited user account upgrades
Attachment #569103 - Flags: review?(robert.bugzilla)
Summary of changes since v1 of the patch:
- Maintenance service page aligned with text to the left instead of having 15px margin 
- Shortcuts page checkboxes aligned with text to the left instead of having 15px margin
- Installation type radio buttons aligned with text to the left instead of having 15px margin.  
  Associated text for each radio button moved from 30px to 15px.
Attachment #568686 - Attachment is obsolete: true
Attachment #569109 - Flags: review?(robert.bugzilla)
Attachment #568686 - Flags: review?(robert.bugzilla)
Radio button and checkbox alignment changes.

For context of why this change was done, please see:
comment 105, comment 110, comment 113, and comment 150.
Attachment #569110 - Flags: ui-review?(faaborg)
Attachment #569110 - Flags: feedback?(robert.bugzilla)
(In reply to Brian R. Bondy [:bbondy] from comment #146)
> re: rs
> > Though it is unlikely it is possible that another binary for a different 
> > app might be named maintenanceservice.exe. Might be a good thing to name 
> > it mozmaintenanceservice.exe or mozmaintsvc.exe.
> 
> The same name wouldn't cause a problem for the exe name.
> The svcname is MozillaMaintenance and the friendly name is Mozilla
> Maintenance Service.  Only the svcname could conflict with other services
> and not the exe name.  So I think we should just leave as is.  But if you
> prefer the moz prefix I can add that.
Understood... the reason I asked for this is *if* we ever need to deal with the process from the installer it would be even more likely to be unique and as a side benefit it is easier to spot and associate to Mozilla in the task manager whereas maintenanceservice.exe is so generic that people would have to investigate to figure out what the process is.
Gotcha ok, I'll do that change towards the end as it'll need a refresh of most of the patches.
Comment on attachment 568676 [details] [diff] [review]
Patch 1 - Base service code

>diff --git a/toolkit/components/maintenanceservice/maintenanceservice.cpp b/toolkit/components/maintenanceservice/maintenanceservice.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
>@@ -0,0 +1,285 @@
>+/* ***** BEGIN LICENSE BLOCK *****
>+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
>+ *
>+ * The contents of this file are subject to the Mozilla Public License Version
>+ * 1.1 (the "License"); you may not use this file except in compliance with
>+ * the License. You may obtain a copy of the License at
>+ * http://www.mozilla.org/MPL/
>+ *
>+ * Software distributed under the License is distributed on an "AS IS" basis,
>+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
>+ * for the specific language governing rights and limitations under the
>+ * License.
>+ *
>+ * The Original Code is Application Update service.
s/Application Update service/Mozilla Maintenance service/.
Replace here and elsewhere in the license headers

>...
>+int wmain(int argc, WCHAR **argv)
>+{
>+#ifdef PR_LOGGING
>+  if (!gServiceLog) {
>+    gServiceLog = PR_NewLogModule("nsFirefoxService");
s/nsFirefoxService/nsMaintenanceService/
Here and elsewhere

>+  }
>+#endif
>+
>+  // If command-line parameter is "install", install the service
>+  // or upgrade if already installed.
>+  // If command-line parameter is "upgrade", upgrade the service
>+  // but do not install it if it is not already installed.
>+  // If command line parameter is "uninstall", uninstall the service.
>+  // Otherwise, the service is probably being started by the SCM.
>+  if (lstrcmpi(argv[1], L"install") == 0) {
>+    
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Installing service..."));
nit: for consistency, start code immediately after conditional here and elsewhere.

>+
>+DWORD WINAPI StartMonitoringThreadProc(LPVOID param) 
>+{
>+  StartDirectoryChangeMonitor();
>+  return 0;
>+}
>+
>+// Entry point for the service
>+void WINAPI SvcMain(DWORD dwArgc, LPWSTR *lpszArgv)
>+{
>+#ifdef PR_LOGGING
>+  if (!gServiceLog) {
>+    gServiceLog = PR_NewLogModule("nsFirefoxService");
s/nsFirefoxService/nsMaintenanceService/

>+  }
>+#endif
>+
>+  // Register the handler function for the service
>+  gSvcStatusHandle = RegisterServiceCtrlHandlerW(SVC_NAME, SvcCtrlHandler);
>+  if (!gSvcStatusHandle) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("RegisterServiceCtrlHandler failed (%d)", ::GetLastError()));
>+    return; 
>+  } 
>+
>+  // These SERVICE_STATUS members remain as set here
>+  gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
>+  gSvcStatus.dwServiceSpecificExitCode = 0;
>+
>+  // Report initial status to the SCM
>+  ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
>+
>+  // Perform service-specific initialization and work.
>+  SvcInit(dwArgc, lpszArgv);
>+}
>+
>+// Service initialization
>+void SvcInit(DWORD dwArgc, LPWSTR *lpszArgv)
>+{
>+  // Be sure to periodically call ReportSvcStatus() with 
>+  // SERVICE_START_PENDING. If initialization fails, call
>+  // ReportSvcStatus with SERVICE_STOPPED.
This comment is vague especially regarding what periodically actually means.

The second half regarding SERVICE_STOPPED should be before the call to ReportSvcStatus when initialization fails and it should note that initialization has failed.

>+
>+  // Create an event. The control handler function, SvcCtrlHandler,
>+  // signals this event when it receives the stop control code.
>+  ghSvcStopEvent = CreateEvent(
>+    NULL,    // default security attributes
>+    TRUE,    // manual reset event
>+    FALSE,   // not signaled
>+    NULL);   // no name
>+
>+  if (NULL == ghSvcStopEvent) {
>+    ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
Is NO_ERROR correct for this case?

>+    return;
>+  }
>+
>+  DWORD threadID;
>+  HANDLE thread = ::CreateThread(NULL, 0, StartMonitoringThreadProc, 0, 0, &threadID);
>+
>+  // Report running status when initialization is complete.
>+  ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
>+
>+
>+  // Perform work until service stops.
>+  for(;;) {
>+    // Check whether to stop the service.
>+    WaitForSingleObject(ghSvcStopEvent, INFINITE);
>+
>+    WCHAR stopFilePath[MAX_PATH +1];
>+    if (!GetUpdateDirectoryPath(stopFilePath)) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not obtain update directory path, terminating thread forcefully."));
>+      TerminateThread(thread, 1);
>+    }
>+
>+    gServiceStopping = true;
>+    if (!::PathAppendSafe(stopFilePath, L"stop")) {
Please add a comment regarding the purpose of the stop file.

>+      TerminateThread(thread, 2);
>+    }
>+    HANDLE stopFile = CreateFile(L"stop", GENERIC_READ, 0, 
>+                                 NULL, CREATE_ALWAYS, 0, NULL);
stopFilePath?

>+    if (stopFile == INVALID_HANDLE_VALUE) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not create stop file, terminating thread forcefully."));
>+      TerminateThread(thread, 3);
>+    } else {
>+      CloseHandle(stopFile);
>+      DeleteFile(stopFilePath);
>+    }
>+
>+    ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
>+    return;
>+  }
>+}
>+
>+// Sets the current service status and reports it to the SCM.
>+// Parameters:
>+//   dwCurrentState - The current state (see SERVICE_STATUS)
>+//   dwWin32ExitCode - The system error code
>+//   dwWaitHint - Estimated time for pending operation, 
>+//     in milliseconds
Please use a javadoc style comment

>+void ReportSvcStatus(DWORD dwCurrentState, 
>+                     DWORD dwWin32ExitCode, 
>+                     DWORD dwWaitHint)
>+{
>+  static DWORD dwCheckPoint = 1;
>+
>+  // Fill in the SERVICE_STATUS structure.
>+  gSvcStatus.dwCurrentState = dwCurrentState;
>+  gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
>+  gSvcStatus.dwWaitHint = dwWaitHint;
>+
>+  if (SERVICE_START_PENDING == dwCurrentState) {
>+    gSvcStatus.dwControlsAccepted = 0;
>+  } else {
>+    gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
>+  }
>+
>+  if ((SERVICE_RUNNING == dwCurrentState) ||
>+      (SERVICE_STOPPED == dwCurrentState)) {
>+    gSvcStatus.dwCheckPoint = 0;
>+  } else {
>+    gSvcStatus.dwCheckPoint = dwCheckPoint++;
>+  }
>+
>+  // Report the status of the service to the SCM.
>+  SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
>+}
>+
>+// Called by SCM whenever a control code is sent to the service
>+// using the ControlService function.
>+void WINAPI SvcCtrlHandler(DWORD dwCtrl)
>+{
>+  // Handle the requested control code. 
>+  switch(dwCtrl) {
>+  case SERVICE_CONTROL_STOP: 
>+    ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
>+
>+    // Signal the service to stop.
>+    SetEvent(ghSvcStopEvent);
>+    ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
>+    return;
nit: break;

>+  case SERVICE_CONTROL_INTERROGATE: 
>+    break; 
>+  default: 
>+    break;
>+  } 
>+}
Comment on attachment 568676 [details] [diff] [review]
Patch 1 - Base service code

>diff --git a/toolkit/components/maintenanceservice/maintenanceservice.exe.manifest b/toolkit/components/maintenanceservice/maintenanceservice.exe.manifest
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/maintenanceservice.exe.manifest
>@@ -0,0 +1,34 @@
>+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
>+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
>+<assemblyIdentity
>+        version="1.0.0.0"
>+        processorArchitecture="*"
>+        name="Updater"
Please change Updater to something like MaintenanceService

>+        type="win32"
>+/>
>+<description>Updater</description>
>+<dependency>
>+        <dependentAssembly>
>+                <assemblyIdentity
>+                        type="win32"
>+                        name="Microsoft.Windows.Common-Controls"
>+                        version="6.0.0.0"
>+                        processorArchitecture="*"
>+                        publicKeyToken="6595b64144ccf1df"
>+                        language="*"
>+                />
Does this really need common controls? I think this is just a copy / paste inclusion from another manifest.

>+        </dependentAssembly>
>+</dependency>
>+<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
>+  <ms_asmv3:security>
>+    <ms_asmv3:requestedPrivileges>
>+      <ms_asmv3:requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
Out of curiousity, could this safely be asInvoker instead? If so, is there any reason not to just use asInvoker?

>+    </ms_asmv3:requestedPrivileges>
>+  </ms_asmv3:security>
>+</ms_asmv3:trustInfo>
>+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
>+    <application>
>+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
Why isn't Vista included here?

>+    </application>
>+  </compatibility>
>+</assembly>
Comment on attachment 568676 [details] [diff] [review]
Patch 1 - Base service code

>diff --git a/toolkit/components/maintenanceservice/servicebase.cpp b/toolkit/components/maintenanceservice/servicebase.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/servicebase.cpp
>...

>+#include "servicebase.h"
>+
>+// Shared code with Firefox and updater.exe
I don't think this comment adds much value but if you want it go with something like
//Shared code between applications and updater.exe

>+#include "nsWindowsRestart.cpp"
>diff --git a/toolkit/components/maintenanceservice/servicebase.h b/toolkit/components/maintenanceservice/servicebase.h
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/servicebase.h
>@@ -0,0 +1,45 @@
>+/* ***** BEGIN LICENSE BLOCK *****
>+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
>+ *
>+ * The contents of this file are subject to the Mozilla Public License Version
>+ * 1.1 (the "License"); you may not use this file except in compliance with
>+ * the License. You may obtain a copy of the License at
>+ * http://www.mozilla.org/MPL/
>+ *
>+ * Software distributed under the License is distributed on an "AS IS" basis,
>+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
>+ * for the specific language governing rights and limitations under the
>+ * License.
>+ *
>+ * The Original Code is Application Update service base code.
Mozilla Maintenance service.

Looks good though I'd like another quick pass at this before r+'ing
Attachment #568676 - Flags: review?(robert.bugzilla) → review-
Attachment #569110 - Flags: feedback?(robert.bugzilla) → feedback+
Comment on attachment 568993 [details] [diff] [review]
Patch 2 - Main service logic. v2

>diff --git a/toolkit/components/maintenanceservice/workmonitor.cpp b/toolkit/components/maintenanceservice/workmonitor.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
>@@ -0,0 +1,417 @@
>+/* ***** BEGIN LICENSE BLOCK *****
>+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
>+ *
>+ * The contents of this file are subject to the Mozilla Public License Version
>+ * 1.1 (the "License"); you may not use this file except in compliance with
>+ * the License. You may obtain a copy of the License at
>+ * http://www.mozilla.org/MPL/
>+ *
>+ * Software distributed under the License is distributed on an "AS IS" basis,
>+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
>+ * for the specific language governing rights and limitations under the
>+ * License.
>+ *
>+ * The Original Code is Application Update service fs monitoring.
s/Application Update service fs/Maintenance service file system monitoring/

>...
>+static const int FILE_SHARE_ALL = FILE_SHARE_READ | 
>+                                  FILE_SHARE_WRITE | 
>+                                  FILE_SHARE_DELETE;
>+
>+// Wait 5 minutes for an update operation to run at most.
>+static const int TIME_TO_WAIT_ON_UPDATER = 5 * 60 * 1000;
note: we have had bug reports from people that update an install on a share with a very slow connection. I think this timeout should be sufficient... just calling it out.

>+
>+HANDLE QueryUserToken(DWORD sessionID)
>+{
>+  HMODULE module = LoadLibraryW(L"wtsapi32.dll");
>+  HANDLE token = NULL;
>+  LPWTSQueryUserToken wtsQueryUserToken = (LPWTSQueryUserToken)GetProcAddress(module, "WTSQueryUserToken");
>+  if (wtsQueryUserToken) {
>+    wtsQueryUserToken(sessionID, &token);
>+  }
>+  FreeModule(module);
>+  return token;
>+}
>+
>+HANDLE GetElevatedTokenFromToken(HANDLE token) 
>+{
>+  // Magic below...
>+  // UAC creates 2 tokens.  One is the restricted token which we have.
>+  // the other is the UAC elevated one. Since we are running as a service
>+  // as the system account we have access to both.
nit: this seems like it would be better to have as a javadoc function comment
http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html

>+  TOKEN_LINKED_TOKEN tlt;
>+  HANDLE hNewLinkedToken = NULL;
>+  DWORD len;
>+  if(::GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, 
>+                           &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) {
>+    token = tlt.LinkedToken;
>+    hNewLinkedToken = token;
>+  }
>+  return hNewLinkedToken;
>+}
>+
>+// Returns TRUE if the process was run and returned 0
nit: always use javadoc style comments for documenting functions

>+BOOL StartElevatedProcessInSession(DWORD sessionID, LPCWSTR appToStart, LPCWSTR workingDir, LPWSTR cmdLine)
>+{
>+  BOOL processStarted;
>+  DWORD myProcessID = GetCurrentProcessId();
>+  DWORD mySessionID;
>+  ProcessIdToSessionId(myProcessID, &mySessionID);
>+
>+  STARTUPINFO si = {0};
>+  si.cb = sizeof(STARTUPINFO);
>+  si.lpDesktop = L"winsta0\\Default";
>+  PROCESS_INFORMATION pi = {0};
>+
>+  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+    ("Starting process in an elevated session.  Service session ID: %d; Requested session ID: %d", mySessionID, sessionID));
Attempting to start process in an elevated session

There are a few lines greater than 80 that could fairly easily be under 80

>+
>+  // We must be running this from an older OS than Vista or else
>+  // we are running this code as a user process instead of via the service.
>+  // In that case just execute the process in the normal way.
>+  if (mySessionID == sessionID) {
>+    processStarted = ::CreateProcessW(appToStart, cmdLine, 
>+                                      NULL, NULL, FALSE, 
>+                                      CREATE_DEFAULT_ERROR_MODE | 
>+                                      CREATE_UNICODE_ENVIRONMENT, 
>+                                      NULL, workingDir, &si, &pi);
>+    DWORD lastError = ::GetLastError();
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not create process as current user, last error: %d; appToStart: %S; cmdLine: %S", 
>+       lastError, appToStart, cmdLine));
This is always logged... should be conditional
if (!processStarted) {
...
}

>+
nit: extra blank line

>+  } else {
>+    HANDLE userToken = QueryUserToken(sessionID);
>+
>+    // Check if we are running Vista or later.
>+    OSVERSIONINFO osInfo;
>+    osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
>+    if (GetVersionEx(&osInfo) && osInfo.dwMajorVersion >= 6) {
>+
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Windows Vista or later detected, attempting to find linked token."));
>+
>+      HANDLE elevatedToken = GetElevatedTokenFromToken(userToken);
>+      if (elevatedToken) {
>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+          ("Elevated token was found, using it."));
>+
>+        ::CloseHandle(userToken); 
>+        userToken = elevatedToken;
>+      } else {
>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+          ("An elevated token was not found, will use regular token."));
>+      }
>+    }
>+
>+    // Create an environment block for the process we're about to start using the
>+    // user's token.
>+    LPVOID lpvEnv(NULL);
>+    if (!::CreateEnvironmentBlock(&lpvEnv, userToken, TRUE)) {
>+      
nit: extra blank line here and else where
In case you haven't seen it
https://developer.mozilla.org/En/Mozilla_Coding_Style_Guide

>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not create an environment block, setting it to NULL."));
>+
>+      lpvEnv = NULL;
>+    }
>+
>+    // Launch the process in the specified working directory application and command line
>+    processStarted = ::CreateProcessAsUserW(userToken, appToStart, cmdLine, 
>+                                            NULL, NULL, FALSE,
>+                                            CREATE_DEFAULT_ERROR_MODE | 
>+                                            CREATE_UNICODE_ENVIRONMENT, 
>+                                            lpvEnv, workingDir, &si, &pi);
>+    if (!processStarted) {
>+      DWORD lastError = ::GetLastError();
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not create process with user token, last error: %d; appToStart: %S; cmdLine: %S", 
>+         lastError, appToStart, cmdLine));
>+    }
>+    ::CloseHandle(userToken);
>+  }
>+
>+  if (processStarted) {
>+
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Process was started... waiting on result.")); 
>+
>+    // Wait for the updater process to finish
>+    ::WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER);
>+
>+    DWORD returnCode;
>+    if (!GetExitCodeProcess(pi.hProcess, &returnCode)) {
nit: consistency :: - please check other usage as well.
if (!::GetExitCodeProcess(pi.hProcess, &returnCode)) {

>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Process finished but could not obtain return code.")); 
>+      return FALSE;
>+    }
>+
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Process finished with return code %d.", returnCode)); 
>+
>+    // updater returns 0 if successful.
>+    return returnCode == 0;
>+  } else {
>+    // The process couldn't be started.
>+    return FALSE;
>+  }
>+}
>+
>+// Returns true if we want the service to stop
>+// For now this is every time we process a .mz file.
nit: javadoc comment - here and else where as applicable

>+bool ProcessWorkItem(LPCWSTR monitoringBasePath, FILE_NOTIFY_INFORMATION &notifyInfo)
>+{
>+  int filenameLength = notifyInfo.FileNameLength / 
>+                       sizeof(notifyInfo.FileName[0]); 
>+
>+  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+    ("Processing new command meta file: %S", notifyInfo.FileName));
>+
>+  // When the file is ready for processing it will be renamed 
>+  // to have a .mz extension
>+  bool haveWorkItem = notifyInfo.Action == FILE_ACTION_RENAMED_NEW_NAME && 
>+                      notifyInfo.FileNameLength > 3 && 
>+                      notifyInfo.FileName[filenameLength - 3] == L'.' &&
>+                      notifyInfo.FileName[filenameLength - 2] == L'm' &&
>+                      notifyInfo.FileName[filenameLength - 1] == L'z';
>+  if (!haveWorkItem) {
>+    // We don't have a work item, keep looking
>+    return false;
consistency nit: use uppercase FALSE

>+  }
>+
>+  WCHAR fullMetaUpdateFilePath[MAX_PATH+1];
nit: MAX_PATH + 1 here and elsewhere

>+  wcscpy(fullMetaUpdateFilePath, monitoringBasePath);
>+
>+  // We only support file paths in monitoring directories that are MAX_PATH chars or less.
>+  if (!PathAppendSafe(fullMetaUpdateFilePath, notifyInfo.FileName)) {
>+    // Cannot process update, skipfileSize it.
>+    return true;
nit: consistency - uppercase TRUE

>+  }
>+
>+  nsAutoHandle metaUpdateFile = ::CreateFile(fullMetaUpdateFilePath, GENERIC_READ, 
>+                                             FILE_SHARE_ALL, NULL, OPEN_EXISTING,
>+                                             0, NULL);
>+  if (INVALID_HANDLE_VALUE == metaUpdateFile) {
>+
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not open command meta file: %S", notifyInfo.FileName));
>+    return true;
>+  }
>+
>+  DWORD fileSize = GetFileSize(metaUpdateFile, NULL);
>+  DWORD sessionID = 0;
>+  // The file should be in WIDECHAR's so if it's of odd size it's
s/WIDECHAR's/wide characters/

>+  // an invalid file
>+  const int kSanityCheckFileSize = 1024*64;
nit: 1024 * 64

>+  if (fileSize == INVALID_FILE_SIZE || 
>+      (fileSize %2) != 0 ||
>+      fileSize > kSanityCheckFileSize ||
>+      fileSize < sizeof(DWORD)) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not obtain file size or an improper file size was encountered for command meta file: %S", notifyInfo.FileName));
>+    return true;
>+  }
>+
>+  // The first 4 bytes are for the process ID
>+  DWORD read1;
nit: use descriptive names here and in the other instances of read#

>+  BOOL result1 = ::ReadFile(metaUpdateFile, &sessionID, 
>+                            sizeof(DWORD), &read1, NULL);
>+  fileSize -= sizeof(DWORD);
>+
>+  // The next MAX_PATH wchar's are for the app to start
>+  WCHAR appToStart[MAX_PATH + 1];
>+  ZeroMemory(appToStart, sizeof(appToStart));
>+  DWORD read2;
>+  BOOL result2 = ::ReadFile(metaUpdateFile, appToStart, 
>+                            MAX_PATH * sizeof(WCHAR), &read2, NULL);
How about
BOOL result;
result |= ::ReadFile
result |= ::ReadFile
etc.

>+  fileSize -= read2;
>+
>+  // The next MAX_PATH wchar's are for the app to start
>+  WCHAR workingDirectory[MAX_PATH + 1];
>+  ZeroMemory(workingDirectory, sizeof(workingDirectory));
>+  DWORD read3;
>+  BOOL result3 = ::ReadFile(metaUpdateFile, workingDirectory, 
>+                            MAX_PATH * sizeof(WCHAR), &read3, NULL);
>+  fileSize -= read3;
>+
>+  // + 2 for wide char termination
>+  nsAutoArrayPtr<char> cmdlineBuffer = new char[fileSize + 2];
>+  DWORD cmdLineBufferRead;
>+  BOOL result4 = ::ReadFile(metaUpdateFile, cmdlineBuffer, 
>+                            fileSize, &cmdLineBufferRead, NULL);
>+  fileSize -= cmdLineBufferRead;
>+
>+  if (!result1 || !result2 || !result3 || !result4 ||
>+      read1 != sizeof(DWORD) || 
>+      read2 != MAX_PATH * sizeof(WCHAR) ||
>+      read3 != MAX_PATH * sizeof(WCHAR) ||
>+      fileSize != 0) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not read command data for command meta file: %S", notifyInfo.FileName));
>+    return true;
>+  }
>+  cmdlineBuffer[cmdLineBufferRead] = L'\0';
>+  cmdlineBuffer[cmdLineBufferRead + 1] = '\0';
>+  WCHAR *cmdlineBufferWide = reinterpret_cast<WCHAR*>(cmdlineBuffer.get());
>+
>+  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+    ("An update command was detected and is being processed for command meta file: %S", notifyInfo.FileName));
>+
>+  // Validate the certificate of the app the user wants us to start.
>+  // Also check to make sure the certificate is trusted.
>+#ifndef DISABLE_SERVICE_AUTHENTICODE_CHECK
>+  if (CheckIfBinaryMatchesAllowedCertificates(appToStart)) {
>+#endif
>+    if (!StartElevatedProcessInSession(sessionID, appToStart, workingDirectory, cmdlineBufferWide)) {
>+      // TODO: Need to tell the app that we couldn't run the update so 
>+      // the app will do the update the old way. rs do you think via update status file?
>+      // I don't want it to keep using the service in case it keeps getting 
>+      // the same error on each launch.
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Error running process in session %d.  Last error: %d", sessionID, GetLastError()));
>+    } 
>+#ifndef DISABLE_SERVICE_AUTHENTICODE_CHECK
Seems like this should be for the else branch below

>+    else {
>+      // The update was executed and run successfully
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Updater.exe was launched and run successfully"));
nit: updater.exe

>+    }
>+  } else {
>+    // TODO: Need to tell the app that we couldn't run the update
>+    // because the cert is not valid.  The app will do the update 
>+    // itself the old way.  rs do you think via update status file?
>+    // I don't want it to keep using the service in case it keeps getting 
>+    // the same error on each launch.
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not start process due to certificate check.  Last error: %d", GetLastError()));
For the TODO's above a new error code for the update.status file should suffice here
http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/readstrings/errors.h

I'd like to separate the readstrings error codes and the updater error codes but not in this bug.

>+  } 
>+#endif
>+
>+  // We processed a work item, whether or not it was successful.
>+  return true;
>+}
>+
>+
>+BOOL StartDirectoryChangeMonitor() 
>+{
>+  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+    ("Starting directory change monitor..."));
>+
>+  // Init the update directory path and ensure it exists.
>+  // Example: C:\programData\Mozilla\Firefox\updates\[channel]
>+  // The channel is not included here as we want to monitor the base directory
>+  WCHAR programData[MAX_PATH+1];
perhaps name this updateData or something similar to avoid confusion with c:\programdata\

>+  if (!GetUpdateDirectoryPath(programData)) {
>+
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not obtain update directory path"));
>+
>+    return FALSE;
>+  }

>diff --git a/toolkit/components/maintenanceservice/workmonitor.h b/toolkit/components/maintenanceservice/workmonitor.h
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/workmonitor.h
>@@ -0,0 +1,41 @@
>+/* ***** BEGIN LICENSE BLOCK *****
>+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
>+ *
>+ * The contents of this file are subject to the Mozilla Public License Version
>+ * 1.1 (the "License"); you may not use this file except in compliance with
>+ * the License. You may obtain a copy of the License at
>+ * http://www.mozilla.org/MPL/
>+ *
>+ * Software distributed under the License is distributed on an "AS IS" basis,
>+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
>+ * for the specific language governing rights and limitations under the
>+ * License.
>+ *
>+ * The Original Code is Application Update service fs monitoring.
s/Application Update service fs/Maintenance service file system monitoring/

Also, looks good though I need to dive into this one a bit more.
Attachment #568993 - Flags: review?(robert.bugzilla) → review-
Attachment #568949 - Attachment is obsolete: true
Attachment #568265 - Attachment is obsolete: true
Attachment #568674 - Attachment is obsolete: true
Attachment #567667 - Attachment is obsolete: true
Comment on attachment 568678 [details] [diff] [review]
Patch 3 - Code for installing the service

I need to dive into this one a bit deeper but I wanted to give you what I have so far so you have a chance to address the version check comment before I am done.

>diff --git a/toolkit/components/maintenanceservice/serviceinstall.cpp b/toolkit/components/maintenanceservice/serviceinstall.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/serviceinstall.cpp
>@@ -0,0 +1,382 @@
>+/* ***** BEGIN LICENSE BLOCK *****
>+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
>+ *
>+ * The contents of this file are subject to the Mozilla Public License Version
>+ * 1.1 (the "License"); you may not use this file except in compliance with
>+ * the License. You may obtain a copy of the License at
>+ * http://www.mozilla.org/MPL/
>+ *
>+ * Software distributed under the License is distributed on an "AS IS" basis,
>+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
>+ * for the specific language governing rights and limitations under the
>+ * License.
>+ *
>+ * The Original Code is Application Update service.
s/Application Update service/Maintenance service/

Not going to call this out in further cases nut please change here and else where.


>+#include <windows.h>
>+#include <aclapi.h>
>+#include <stdlib.h>
>+
>+#include <nsAutoPtr.h>
>+#include <nsAutoServiceHandle.h>
>+#include <nsMemory.h>
>+
>+#include "serviceinstall.h"
>+#include "servicebase.h"
>+#include "shellapi.h"
>+
>+#pragma comment(lib, "version.lib")
>+#define DEFERRED_DELETE_TIMEOUT_SECONDS 10
>+
>+static bool GetVersionNumberFromPath(LPWSTR path, DWORD &version) 
>+{
>+  DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0);
>+  nsAutoArrayPtr<char> fileVersionInfo = new char[fileVersionInfoSize];
>+  if (!GetFileVersionInfoW(path, 0, 
>+    fileVersionInfoSize, fileVersionInfo.get())) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not obtain file info of old service.  (%d)", GetLastError()));
>+      return false;
>+  }
>+
>+  VS_FIXEDFILEINFO *fixedFileInfo = 
>+    reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get());
>+  UINT size;
>+  if (!VerQueryValueW(fileVersionInfo.get(), L"\\", 
>+    reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not query file version info of old service.  (%d)", GetLastError()));
>+      return false;
>+  }  
>+
>+  version = fixedFileInfo->dwFileVersionLS;
I don't think build number will be enough here. Need something like CVersionInfo since it supports full version comparison.
http://www.codeguru.com/cpp/w-p/win32/versioning/article.php/c4539

There is also the following which compares version strings
http://mxr.mozilla.org/mozilla-central/source/xpcom/glue/nsVersionComparator.cpp

>+  return true;
>+}
>+
>+// Return value does not indicate anything except that the 
>+// operation was attempted
>+DWORD DeferredDeletePath(LPCWSTR pathToDelete) 
>+{
>+  WCHAR deferredSlefDeleteCmdLine[MAX_PATH + 32];
>+  wsprintfW(deferredSlefDeleteCmdLine, 
>+            L"/C SLEEP %i && DEL \"%s\"", 
>+            DEFERRED_DELETE_TIMEOUT_SECONDS, 
>+            pathToDelete);
>+
>+  SetLastError(ERROR_SUCCESS);
>+  ShellExecuteW(NULL, L"open", L"cmd", deferredSlefDeleteCmdLine, NULL, SW_HIDE);
>+  return GetLastError();
>+}
>+
>+bool SvcInstall(bool upgradeOnly)
>+{
>+  // Get a handle to the local computer SCM database with full access rights.  
>+  nsAutoServiceHandle schSCManager(OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS));
>+  if (!schSCManager) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not open service manager.  (%d)", GetLastError()));
>+    return false;
>+  }
>+
>+  WCHAR modulePath[MAX_PATH + 1];
>+  if(!GetModuleFileNameW(NULL, modulePath, NS_ARRAY_LENGTH(modulePath))) {
nit: I'd prefer if you used sizeof(modulePath)/sizeof(modulePath[0]) since this is standalone code.
Minor change (removed static for one function) didn't want to put in another patch to avoid future rebasing.
Attachment #568680 - Attachment is obsolete: true
Attachment #569605 - Flags: review?(robert.bugzilla)
Attachment #568680 - Flags: review?(robert.bugzilla)
This new patch has handling for launching the callback application with the unelevated token.  
The way the new code works is that it trims off the callback application from the command lines and executes that separately.  It calls updater.exe without the callback information.

I also noticed something we didn't think about which is that the callback app is also specified via command line and therefore is suseptible to session ID spoofing.  It's not a huge deal because we execute it with the unelevated token though.  The is not a new problem in this patch, but also a problem even without this latest patch.  To fix it, I added a new check for certificate checking on the callback app as well.

I think it is fine to not have the callback app (firefox.exe) signed for Nightly (until we have it automated) because the only side effect is that the callback application will not be run after updates.
Attachment #569679 - Flags: review?(robert.bugzilla)
Attached patch Patch 15 - UAC helper functions. (obsolete) — Splinter Review
No changes, just moved some code out of this patch and into patch 2 to avoid future rebasing.
Attachment #569103 - Attachment is obsolete: true
Attachment #569681 - Flags: review?(robert.bugzilla)
Attachment #569103 - Flags: review?(robert.bugzilla)
Attachment #569681 - Attachment description: Patch 15 - Added support for Limited user account upgrades and improved handling of tokens. v2. → Patch 15 - UAC helper functions.
Implemented review comments.
Attachment #568993 - Attachment is obsolete: true
Attachment #569683 - Flags: review?(robert.bugzilla)
Attachment #569605 - Attachment description: XUL Runtime Environment code. v2. → Patch 5 - XUL Runtime Environment code. v2.
Attachment #569605 - Attachment is patch: true
For anyone doing QA on the build, any installer you use after today has the NSPR module changed from nsFirefoxService to nsMaintenanceService.
Re questions on patch 1 review of manifest file:

> Out of curiousity, could this safely be asInvoker instead? 
> If so, is there any reason not to just use asInvoker?

There is a lot of code in the service that wouldn't work asInvoker so I think it needs to stay asAdministrator.

>+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
> Why isn't Vista included here?

This was carried over from me originally copying the manifest file from updater.exe.  It is a problem there too, I will include a fix in that manfiest as well in the next patch.
Attached patch Patch 1 - Base service code. v2. (obsolete) — Splinter Review
Implemented review comments.
Attachment #568676 - Attachment is obsolete: true
Attachment #569692 - Flags: review?(robert.bugzilla)
(In reply to Brian R. Bondy [:bbondy] from comment #160)
> Created attachment 569679 [details] [diff] [review] [diff] [details] [review]
> Patch 16 - Support for launching callback app as unelevated.
> 
> This new patch has handling for launching the callback application with the
> unelevated token.  
> The way the new code works is that it trims off the callback application
> from the command lines and executes that separately.  It calls updater.exe
> without the callback information.
> 
> I also noticed something we didn't think about which is that the callback
> app is also specified via command line and therefore is suseptible to
> session ID spoofing.  It's not a huge deal because we execute it with the
> unelevated token though.  The is not a new problem in this patch, but also a
> problem even without this latest patch.  To fix it, I added a new check for
> certificate checking on the callback app as well.
> 
> I think it is fine to not have the callback app (firefox.exe) signed for
> Nightly (until we have it automated) because the only side effect is that
> the callback application will not be run after updates.

i took a look through this and it looks good, checking the signature on the callback app helps mitigate some of my worries about the session id spoofing problem as well.
Implemented review comments (+some review comments from other patches into this one) and better version checking.

nsVersionComparator was not used because I'd have to link to more stuff and it works with strings, but I have the version components in DWORDs.
The codeguru code uses the same APIs as I was using but did save me some time to see how to get the other version components.
Only a small change was needed from my existing code to check the other 3 components of the version.
Attachment #568678 - Attachment is obsolete: true
Attachment #569744 - Flags: review?(robert.bugzilla)
Attachment #568678 - Flags: review?(robert.bugzilla)
Many of the PR_LOG messages seem appropriate as event log messages. If you think it would be a good thing to add please file a followup bug.
I had some code initially that did event log messages (in the first base patch which is now obsolete) but you need to have a special compiling step and also I wasn't sure about how to manage the localization. I know it would be good for enterprise customers though.  I'll post a follow up bug for it, thanks for the suggestion.
Blocks: 697543
Comment on attachment 568685 [details] [diff] [review]
Patch 8 - NSPR wide string logging support on Windows

This needs to be reviewed by one of the NSPR people such as wtc@google.com.

Before asking for review it would be a good thing to make the indentation consistent with the rest of the file.
Attachment #568685 - Flags: review?(robert.bugzilla) → review-
> Before asking for review it would be a good thing to make the 
> indentation consistent with the rest of the file.

Sorry looked aligned in my local patch file (tortoiseHg) and Visual Studio. 
They use a lot of mixed tab/space characters.

I'll do th same as them and re-submit.
Comment on attachment 568679 [details] [diff] [review]
Patch 4 - Certificate check code inside service

># HG changeset patch
># Parent f04152f2f05006759cc53b386dcdc35e81035d90
># User Brian R. Bondy <netzen@gmail.com>
>Bug481815 - Windows silent update service; Certificate check code
>
>diff --git a/toolkit/components/maintenanceservice/certificatecheck.cpp b/toolkit/components/maintenanceservice/certificatecheck.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/certificatecheck.cpp
>@@ -0,0 +1,545 @@
>+/* ***** BEGIN LICENSE BLOCK *****
>+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
>+ *
>+ * The contents of this file are subject to the Mozilla Public License Version
>+ * 1.1 (the "License"); you may not use this file except in compliance with
>+ * the License. You may obtain a copy of the License at
>+ * http://www.mozilla.org/MPL/
>+ *
>+ * Software distributed under the License is distributed on an "AS IS" basis,
>+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
>+ * for the specific language governing rights and limitations under the
>+ * License.
>+ *
>+ * The Original Code is certificate check code
Maintenance service certificate check code.

here and else where

>+ *
>+ * The Initial Developer of the Original Code is
>+ * Mozilla Foundation.
>+ * Portions created by the Initial Developer are Copyright (C) 2011
>+ * the Initial Developer. All Rights Reserved.
>+ *
>+ * Contributor(s):
>+ *   Brian R. Bondy <netzen@gmail.com>
>+ *
>+ * Alternatively, the contents of this file may be used under the terms of
>+ * either the GNU General Public License Version 2 or later (the "GPL"), or
>+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
>+ * in which case the provisions of the GPL or the LGPL are applicable instead
>+ * of those above. If you wish to allow use of your version of this file only
>+ * under the terms of either the GPL or the LGPL, and not to allow others to
>+ * use your version of this file under the terms of the MPL, indicate your
>+ * decision by deleting the provisions above and replace them with the notice
>+ * and other provisions required by the GPL or the LGPL. If you do not delete
>+ * the provisions above, a recipient may use your version of this file under
>+ * the terms of any one of the MPL, the GPL or the LGPL.
>+ *
>+ * ***** END LICENSE BLOCK ***** */
>+
>+#include <stdio.h>
>+#include <stdlib.h>
>+#include <windows.h>
>+#include <softpub.h>
>+#include <wintrust.h>
>+
>+#include "certificatecheck.h"
>+#include "servicebase.h"
>+
>+// Link with the Wintrust.lib file.
This comment isn't of any value... is it?

>+#pragma comment(lib, "wintrust.lib")
>+#pragma comment(lib, "crypt32.lib")
>+
>+static const int ENCODING = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
>+
>+
>+// Checks to see if a PE file stored at filename matches the specified issuer
>+// and name.
javadoc comment please

Ideally, we would allow the application to specify what cert attributes to check though I think this is fine at least for now.

>+DWORD CheckCertificateForPEFile(LPCWSTR filename, 
>+                                CertificateCheckInfo &infoToMatch)
filename is really a filepath unless I am mistaken

nit: forgot the following from style guide here and elsewhere... I'm a tad of a stickler with following the style guide with new files.
int
MyFunction(...)
{
  ...
}

int
MyClass::Method(...)
{
  ...
}

>+{
>+  HCERTSTORE hStore = NULL;
>+  HCRYPTMSG hMsg = NULL; 
>+  PCCERT_CONTEXT pCertContext = NULL;
>+  PCMSG_SIGNER_INFO pSignerInfo = NULL;
>+  DWORD lastError = ERROR_SUCCESS;
>+  PublisherInfo progPubInfo;
>+  ZeroMemory(&progPubInfo, sizeof(progPubInfo));
>+
>+  // SEH like exception handling is only used for cleanup of ugly C API calls.
>+  __try
>+  {
nit: __try {

>+    // Get the HCERTSTORE and HCRYPTMSG from the signed file.
>+    DWORD dwEncoding, dwContentType, dwFormatType;
>+    BOOL result = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
>+                                   filename, 
>+                                   CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
>+                                   CERT_QUERY_CONTENT_FLAG_ALL, 
>+                                   0, &dwEncoding, &dwContentType,
>+                                   &dwFormatType, &hStore, &hMsg, NULL);

>...
>+    }
>+  }
>+  __finally
>+  {
nit: __finally {

>+    if (progPubInfo.programName) {
>+      LocalFree(progPubInfo.programName);
>+    }
>+    if (progPubInfo.publisherLink) {

>...
>+// Returns FALSE if the issuer or name do not match or if any error 
>+// occurs in the check
javadoc comment please

>+BOOL DoesCertificateMatch(PCCERT_CONTEXT pCertContext, 
>+                          CertificateCheckInfo &infoToMatch)
>+{
>+  DWORD dwData;

>...
>+LPWSTR AllocateAndCopyWideString(LPCWSTR inputString)
>+{
>+  LPWSTR outputString = NULL;
>+
>+  outputString = (LPWSTR)LocalAlloc(LPTR,
>+    (wcslen(inputString) + 1) * sizeof(WCHAR));
>+  if (outputString != NULL)
>+  {
nit:
if (outputString != NULL) {

>+    lstrcpyW(outputString, inputString);
>+  }
>+  return outputString;
>+}
>+
>+BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,
>+                             PublisherInfo &info)
>+{
>+  BOOL result = FALSE;
>+  PSPC_SP_OPUS_INFO OpusInfo = NULL;  
>+  DWORD dwData;
>+
>+  __try
>+  {
nit: __try {

>+    // Loop through authenticated attributes and find SPC_SP_OPUS_INFO_OBJID OID.
>+    for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++) {           
>+      if (lstrcmpA(SPC_SP_OPUS_INFO_OBJID, 
>+                   pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0) {
>+        // Get Size of SPC_SP_OPUS_INFO structure.
>+        _CRYPTOAPI_BLOB &attr1 = pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0];
>+        result = CryptDecodeObject(ENCODING,

>...
>+        // Fill in Program Name if present.
>+        if (OpusInfo->pwszProgramName) {
>+          info.programName =
>+            AllocateAndCopyWideString(OpusInfo->pwszProgramName);
>+        } else {
>+          info.programName = NULL;
>+        }
>+
>+        // Fill in Publisher Information if present.
>+        if (OpusInfo->pPublisherInfo) {
>+          switch (OpusInfo->pPublisherInfo->dwLinkChoice)
>+          {
nit: format as follows from the style guide
https://developer.mozilla.org/En/Mozilla_Coding_Style_Guide

switch (...) {  
  case 1: {  
    // When you need to declare a variable in a switch, put the block in braces  
    int var;  
    break;  
  }  
  case 2:  
    ...  
    break;  
  default:  
    break;  
}

>+          case SPC_URL_LINK_CHOICE:
>+            info.publisherLink =
>+              AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszUrl);
>+            break;
>+
>+          case SPC_FILE_LINK_CHOICE:
>+            info.publisherLink =
>+              AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszFile);
>+            break;
>+
>+          default:
>+            info.publisherLink = NULL;
>+            break;
>+          }
>+        } else {
>+          info.publisherLink = NULL;
>+        }
>+
>+        // Fill in More Info if present.
>+        if (OpusInfo->pMoreInfo) {
>+          switch (OpusInfo->pMoreInfo->dwLinkChoice)
>+          {
nit: same as previous formating of switch

>+          case SPC_URL_LINK_CHOICE:
>+            info.moreInfoLink =
>+              AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszUrl);
>+            break;
>+
>+          case SPC_FILE_LINK_CHOICE:
>+            info.moreInfoLink =
>+              AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszFile);
>+            break;
>+
>+          default:
>+            info.moreInfoLink = NULL;
>+            break;
>+          }
>+        } else {
>+          info.moreInfoLink = NULL;
>+        }
>+        result = TRUE;
>+        break; // Break from for loop.
>+      } // lstrcmp SPC_SP_OPUS_INFO_OBJID                 
>+    } // for 
cleanup

>+  }
>+  __finally
>+  {
nit: __finally {

>...
>+  // WinVerifyTrust verifies signatures as specified by the GUID 
>+  // and Wintrust_Data.
>+  LONG lStatus = WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);
>+  DWORD dwLastError = GetLastError();
>+  BOOL validated = FALSE;
>+  switch (lStatus) {
>+    case ERROR_SUCCESS:
>+      // The hash that represents the subject is trusted and there were no
>+      // verification errors.  No publisher nor time stamp chain errors.
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS, 
>+             ("The file \"%S\" is signed and the signature was verified.",
>+              pwszSourceFile));
>+      validated = TRUE;
>+      break;
>+    case TRUST_E_NOSIGNATURE:
>+      // The file was not signed or had a signature that was not valid.
>+      // Get the reason for no signature.
>+      if (TRUST_E_TIME_STAMP == dwLastError) {
>+        // The file was not signed.
>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS, 
>+               ("The file \"%S\" is had a timestamp error.", pwszSourceFile));
nit: The file \"%S\" has a timestamp error.

>+      } else if (TRUST_E_NOSIGNATURE == dwLastError ||
>+                TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError ||
>+                TRUST_E_PROVIDER_UNKNOWN == dwLastError) {
>+        // The file was not signed.
>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS, 
>+               ("The file \"%S\" is not signed.", pwszSourceFile));
>+      } else {
>+        // The signature was not valid or there was an error 
>+        // opening the file.
>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS, 
>+               ("An unknown error occurred trying to verify the signature of the \"%S\" file.", pwszSourceFile));
nit: I'm not too much of a stickler for line length but this and a few other lines in the other patches are really long.

>+      }
>+      break;
>+    case TRUST_E_EXPLICIT_DISTRUST:
>+      // The hash that represents the subject or the publisher 
>+      // is not allowed by the admin or user.
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS, 
>+             ("The signature is present, but specifically disallowed."));
>+      break;
>+    case TRUST_E_SUBJECT_NOT_TRUSTED:
>+      // The user clicked "No" when asked to install and run.
This implies that we present the user with ui to click "No". Please elaborate.

>diff --git a/toolkit/components/maintenanceservice/certificatecheck.h b/toolkit/components/maintenanceservice/certificatecheck.h
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/certificatecheck.h
>@@ -0,0 +1,65 @@
>...
>+#ifndef _CERTIFICATECHECK_H_
>+#define _CERTIFICATECHECK_H_
>+
>+#include <wincrypt.h>
>+
>+struct PublisherInfo
>+{
>+  LPWSTR programName;
>+  LPWSTR publisherLink;
>+  LPWSTR moreInfoLink;
>+};
>+
>+struct CertificateCheckInfo
>+{
>+  LPCWSTR name;
>+  LPCWSTR issuer;
>+  PublisherInfo signerInfo;
>+};
>+
>+BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo, 
>+                             PublisherInfo &info);
nit: perhaps GetProgramAndPublisherInfo

>+BOOL DoesCertificateMatch(PCCERT_CONTEXT pCertContext, 
>+                          CertificateCheckInfo &infoToMatch);
nit: perhaps DoCertificateAttributesMatch

>+DWORD VerifyCertificateTrustForFile(LPCWSTR pwszSourceFile);
nit: consistency - drop the hungarian notation
nit: I'd just go with filepath

>+DWORD CheckCertificateForPEFile(LPCWSTR filename, 
>+                                CertificateCheckInfo &infoToMatch);
nit: I'd just go with filepath

>+
>+#endif
>diff --git a/toolkit/components/maintenanceservice/registrycertificates.cpp b/toolkit/components/maintenanceservice/registrycertificates.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/registrycertificates.cpp
>@@ -0,0 +1,179 @@
>...
>+BOOL CheckIfBinaryMatchesAllowedCertificates(LPCWSTR application) 
nit: A tad verbose of a name... need to think about it a bit. For example, you have DoesCertificateMatch and this might be better as DoesCertificateMatchFile though that could likely be improved as well.

nit: application is just a filepath.

>+{ 
>+  LPCWSTR maintenanceServiceKey = L"SOFTWARE\\Mozilla\\Certs";
>+
>+  // We use KEY_WOW64_64KEY to always force 64-bit view.
This comment leaves me wondering why... please elaborate.

It isn't clear to me whether it is better to use a common key for all cert attributes for all installs or to have each install store the cert attributes in an install specific key. I am leaning install specific keys since another app should not be able to provide cert attributes that are then used by another app. What do you think?

Holding off on the rest of the registry code review until the above is answered
Attachment #568679 - Flags: review?(robert.bugzilla) → review-
I don't fully understand the whitespacing in that file:
sometimes a line has tab + 4 spaces, sometimes 2 tabs, and sometimes all spaces.

But I made it all consistent with the similar parts of the file.
Attachment #568685 - Attachment is obsolete: true
Attachment #569786 - Flags: review?(robert.bugzilla)
Because of the CSS styling by the way somtimes the green stuff looks 1 space off but it is the same whitespace, I have show whitespace on.
Comment on attachment 569786 [details] [diff] [review]
(PUSHED) Patch 8 - NSPR wide string logging support on Windows. v2.

Thanks! Looks much more consistent with the existing code.

I'm fairly certain that wtc will need to review these changes so changing reviewer.
Attachment #569786 - Flags: review?(robert.bugzilla) → review?(wtc)
Comment on attachment 569605 [details] [diff] [review]
Patch 5 - XUL Runtime Environment code. v2.

>diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp
>--- a/toolkit/xre/nsUpdateDriver.cpp
>+++ b/toolkit/xre/nsUpdateDriver.cpp
>@@ -475,18 +480,29 @@ ApplyUpdate(nsIFile *greDir, nsIFile *up
>     PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
>   }
> 
>   LOG(("spawning updater process [%s]\n", updaterPath.get()));
> 
> #if defined(USE_EXECV)
>   execv(updaterPath.get(), argv);
> #elif defined(XP_WIN)
>-  if (!WinLaunchChild(updaterPathW.get(), argc, argv))
>-    return;
>+
>+  bool attemptToUseService = 
>+    mozilla::Preferences::GetBool(kPrefAppUpdateService, false);
Have you tested this? Unless I'm much mistaken this won't work because we don't actually have a profile at this time. Not sure how this should be handled if this is the case at this time.

>+  // First try to launch the update operation using the service
nit: this should call out "if the user hasn't disabled updating with the service"

>+  if (!attemptToUseService || 
>+      !WinLaunchServiceCommand(updaterPathW.get(), argc, argv)) {
>+    // If that fails then launch the update using updater.exe
nit: remove "If that fails" and just go with "Launch the update using updater.exe"

>+    if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
>+      return;
>+    }
>+  }
>+
>+  // We are going to process an update so we should exit now
>   _exit(0);

>diff --git a/toolkit/xre/nsWindowsRestart.cpp b/toolkit/xre/nsWindowsRestart.cpp
>--- a/toolkit/xre/nsWindowsRestart.cpp
>+++ b/toolkit/xre/nsWindowsRestart.cpp
>...
>@@ -219,16 +230,219 @@ FreeAllocStrings(int argc, PRUnichar **a
>   while (argc) {
>     --argc;
>     delete [] argv[argc];
>   }
> 
>   delete [] argv;
> }
> 
>+BOOL 
>+EnsureWindowsServiceStarted() {
>+  // Get a handle to the SCM database.
>+  nsAutoServiceHandle serviceManager(OpenSCManager(NULL, NULL, 
>+                                                   SC_MANAGER_CONNECT | 
>+                                                   SC_MANAGER_ENUMERATE_SERVICE));
>+  if (!serviceManager)  {
>+    return FALSE;
>+  }
>+
>+  // Get a handle to the service.
>+  nsAutoServiceHandle service(OpenServiceW(serviceManager, 
>+                                           L"MozillaMaintenance", 
>+                                           SERVICE_QUERY_STATUS | SERVICE_START));
>+  if (!service) { 
>+    return FALSE;
>+  }
>+
>+  // Make sure the service is not stopped.
>+  SERVICE_STATUS_PROCESS ssp;
>+  DWORD bytesNeeded;
>+  if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
>+    sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
>+      return FALSE;
>+  }
>+
>+  if (ssp.dwCurrentState == SERVICE_STOPPED) {
>+    if (!::StartService(service, 0, NULL)) {
>+      return FALSE;
>+    }
>+
>+    // Make sure we can get into a started state without waiting too long.
>+    DWORD totalWaitTime = 0;
>+    static const int maxWaitTime = 1000 * 5; // Never wait more than 5 seconds
What is the normal flow for this case? Specifically, does the service typically have to be started in this code path? How long does it take for the service to start on your system?

>+    while (QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
>+        sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
>+      if (ssp.dwCurrentState == SERVICE_RUNNING) {
>+        break;
>+      } else if (ssp.dwCurrentState == SERVICE_START_PENDING &&
>+        totalWaitTime > maxWaitTime) {
>+          // We will probably eventually start, but we can't wait any longer.
>+          break;
>+      } else if (ssp.dwCurrentState != SERVICE_START_PENDING) {
>+        return FALSE;
>+      }
>+
>+      Sleep(ssp.dwWaitHint);
>+      totalWaitTime += (ssp.dwWaitHint + 10);
Where does 10 come from?

>+    }
>+  }
>+
>+  return ssp.dwCurrentState == SERVICE_RUNNING;
>+}
>+
>+
>+BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra)
>+{
>+  if (wcslen(base) + wcslen(extra) > MAX_PATH) {
>+    return FALSE;
>+  }
>+
>+  return ::PathAppendW(base, extra);
>+}
>+
>+BOOL GetUpdateDirectoryPath(WCHAR *path) 
>+{
>+  HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, 
>+    SHGFP_TYPE_CURRENT, path);
>+  if (FAILED(hr)) {
>+    return FALSE;
>+  }
>+  if (!PathAppendSafe(path, L"Mozilla")) {
>+    return FALSE;
>+  }
>+  ::CreateDirectoryW(path, NULL);
>+
>+  if (!PathAppendSafe(path, L"updates")) {
>+    return FALSE;
>+  }
>+  ::CreateDirectoryW(path, NULL);
I am fairly certain that these directories will be created with only the current user and accounts running as admin having write access.

A better method might be to do something similar to XRE_UPDATE_ROOT_DIR and set the permissions on creation.
http://mxr.mozilla.org/mozilla-central/source/toolkit/xre/nsXREDirProvider.cpp#261

Another option is to have the installer set the permissions on the Mozilla directory under CSIDL_COMMON_APPDATA. We already have the NSIS plugin for setting the permissions.

>+  return TRUE;
>+}
>+
>+/**
>+ * Launch a service initiated action with the specified arguments.
>+ * @note argv[0] is ignored
>+ * @note The form of this function that takes char **argv expects UTF-8
>+ */
Include the parameters as well
http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html

>+BOOL
>+WinLaunchServiceCommand(const PRUnichar *exePath, int argc, PRUnichar **argv)
>+{
>+  // Ensure the service is started, if not we should try to start it, if it is
>+  // not in a started state we cannot execute a service command.
s/started state/running state/

>+  if (!EnsureWindowsServiceStarted()) {
>+    return FALSE;
>+  }
>+
>+  WCHAR programData[MAX_PATH + 1];
Perhaps updateData?

>+  if (!GetUpdateDirectoryPath(programData)) {
>+    return FALSE;
>+  }
>+
>+  // Get a new GUID for the filename
I'm curious why you are using a GUID for the filename instead of just a temp file, etc.?

>+  UUID uuid;
>+  ::ZeroMemory(&uuid, sizeof(UUID));
>+  ::UuidCreate(&uuid);
>+  WCHAR* wUUID = NULL;
>+  ::UuidToStringW(&uuid, (RPC_WSTR*)&wUUID);
>+  if(!wUUID) {
>+    return FALSE;
>+  }
>+  
>+  // Generate the filename for the service command
>+  WCHAR fileName[128];
>+  wsprintfW(fileName, L"%s.ma", wUUID);
>+  ::RpcStringFreeW((RPC_WSTR*)&wUUID);
>+  wUUID = NULL;
>+  if (!PathAppendSafe(programData, fileName)) {
>+    return FALSE;
>+  }
>+  const int FILE_SHARE_NONE = 0;
>+  nsAutoHandle updateMetaFile(CreateFileW(programData, GENERIC_WRITE, 
>+                                          FILE_SHARE_NONE, NULL, CREATE_NEW, 
>+                                          0, NULL));
>+  if (updateMetaFile == INVALID_HANDLE_VALUE) {
>+    return FALSE;
>+  }
>+
>+  // Write out the command line arguments that needs to be passed to updater.exe
>+  PRUnichar *commandLineBuffer = MakeCommandLine(argc, argv);
>+  DWORD wrote1, wrote2, wrote3, wrote4;
Please use descriptive names for wrote#

>+  
>+  DWORD sessionID = 0;
>+  ProcessIdToSessionId(GetCurrentProcessId(), &sessionID);
>+  BOOL result1 = ::WriteFile(updateMetaFile, &sessionID, 
>+                             sizeof(DWORD), 
>+                             &wrote1, NULL);
>+
>+  WCHAR appBuffer[MAX_PATH + 1];
>+  ZeroMemory(appBuffer, sizeof(appBuffer));
>+  wcscpy(appBuffer, exePath);
>+  BOOL result2 = ::WriteFile(updateMetaFile, appBuffer, 
>+                             MAX_PATH * sizeof(WCHAR), 
>+                             &wrote2, NULL);
>+
>+  WCHAR workingDirectory[MAX_PATH + 1];
>+  ZeroMemory(workingDirectory, sizeof(appBuffer));
>+  GetCurrentDirectoryW(sizeof(workingDirectory)/sizeof(workingDirectory[0]), 
nit: sizeof(workingDirectory) / sizeof(workingDirectory[0])

>+                       workingDirectory);
>+  BOOL result3 = ::WriteFile(updateMetaFile, workingDirectory, 
>+                             MAX_PATH * sizeof(WCHAR), 
>+                             &wrote3, NULL);
>+
>+  DWORD commandLineLength = wcslen(commandLineBuffer) * sizeof(WCHAR);
>+  BOOL result4 = ::WriteFile(updateMetaFile, commandLineBuffer, 
>+                             commandLineLength, 
>+                             &wrote4, NULL);
How about?
BOOL result;
result |= ::WriteFile
result |= ::WriteFile
etc.

>+  free(commandLineBuffer);
>+  if (!result1 || !result2 || !result3 || !result4 ||
>+      wrote1 != sizeof(DWORD) ||
>+      wrote2 != MAX_PATH * sizeof(WCHAR) ||
>+      wrote3 != MAX_PATH * sizeof(WCHAR) ||
>+      wrote4 != commandLineLength) {
>+    updateMetaFile.forget();
>+    DeleteFileW(programData);
>+    return FALSE;
>+  }
>+
>+  // Note we construct the 'service work' meta object with a ma extension
>+  // When we want the service to start processing it we simply rename it to
>+  // have a .mz extension.  This ensures that the service will never try to
>+  // process a partial update work meta file. 
Would it make sense to create the file in a different directory (temp?) and when it is ready move it to the directory?

>+  updateMetaFile.forget();
>+  WCHAR completedMetaFilePath[MAX_PATH + 1];
>+  wcscpy(completedMetaFilePath, programData);
>+  completedMetaFilePath[wcslen(completedMetaFilePath) -1] = 'z';
>+  return MoveFileExW(programData, completedMetaFilePath, 
>+                     MOVEFILE_REPLACE_EXISTING);
>+}
>+
>+BOOL
>+WinLaunchServiceCommand(const PRUnichar *exePath, int argc, char **argv)
>+{
>+  PRUnichar** argvConverted = new PRUnichar*[argc];
>+  if (!argvConverted)
>+    return FALSE;
>+
>+  for (int i = 0; i < argc; ++i) {
>+    argvConverted[i] = AllocConvertUTF8toUTF16(argv[i]);
>+    if (!argvConverted[i]) {
>+      FreeAllocStrings(i, argvConverted);
>+      return FALSE;
>+    }
>+  }
>+
>+  BOOL ok = WinLaunchServiceCommand(exePath, argc, argvConverted);
>+  FreeAllocStrings(argc, argvConverted);
>+  return ok;
>+}
>+
>+
>+
> /**
>  * Launch a child process with the specified arguments.
>  * @note argv[0] is ignored
>  * @note The form of this function that takes char **argv expects UTF-8
>  */
Include the parameters as well
http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html

> 
> BOOL
> WinLaunchChild(const PRUnichar *exePath, int argc, PRUnichar **argv);
Attachment #569605 - Flags: review?(robert.bugzilla) → review-
Comment on attachment 568950 [details] [diff] [review]
Patch 6 - Firefox preferences update page changes. v2.

># HG changeset patch
># Parent 7de33ffbb8ecba3bd67dcbb39e25f77a2fd01b94
># User Brian R. Bondy <netzen@gmail.com>
>Bug481815 - Windows silent update service; Preferences for whether or not to use the service.
>
>diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
>--- a/browser/app/profile/firefox.js
>+++ b/browser/app/profile/firefox.js
>@@ -184,16 +184,19 @@ pref("app.update.showInstalledUI", false
> 
> // 0 = suppress prompting for incompatibilities if there are updates available
> //     to newer versions of installed addons that resolve them.
> // 1 = suppress prompting for incompatibilities only if there are VersionInfo
> //     updates available to installed addons that resolve them, not newer
> //     versions.
> pref("app.update.incompatible.mode", 0);
> 
>+// Whether or not to attempt using the service for updates.
>+pref("app.update.service", true);
As stated in the last patch, I'm not certain this pref will be available when the update is applied during startup.

minusing until the pref issue is figured out.
Attachment #568950 - Flags: review?(robert.bugzilla) → review-
Comment on attachment 568684 [details] [diff] [review]
Patch 7 - RAII base helpers

Since this is new code under XPCOM let's get Jim to review it and bsmedberg to sr it since he owns XPCOM after it has been reviewed by Jim.
Attachment #568684 - Flags: review?(robert.bugzilla) → review?(jmathies)
>+  bool attemptToUseService = 
>+    mozilla::Preferences::GetBool(kPrefAppUpdateService, false);
> Have you tested this? Unless I'm much mistaken this won't work because we don't
> actually have a profile at this time. Not sure how this should be handled if this
> is the case at this time.

Yup I've tested it.
I did have that problem and but please see Patch 13 - Moving the update check after init.
I forgot that Ted is a peer for NSPR... feel free to change the request
Attachment #569786 - Flags: review?(wtc) → review?(ted.mielczarek)
Comment on attachment 568688 [details] [diff] [review]
Patch 10 - Shared installer code and service upgrade

>diff --git a/browser/installer/windows/nsis/installer.nsi b/browser/installer/windows/nsis/installer.nsi
>--- a/browser/installer/windows/nsis/installer.nsi
>+++ b/browser/installer/windows/nsis/installer.nsi
>...
>@@ -34,16 +35,17 @@
> #
> # ***** END LICENSE BLOCK *****
> 
> # Required Plugins:
> # AppAssocReg   http://nsis.sourceforge.net/Application_Association_Registration_plug-in
> # ApplicationID http://nsis.sourceforge.net/ApplicationID_plug-in
> # ShellLink     http://nsis.sourceforge.net/ShellLink_plug-in
> # UAC           http://nsis.sourceforge.net/UAC_plug-in
>+# SimpleSC      http://nsis.sourceforge.net/NSIS_Simple_Service_Plugin
Remove if you go with the suggested method below

> 
> ; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
> !verbose 3
> 
> ; 7-Zip provides better compression than the lzma from NSIS so we add the files
> ; uncompressed and use 7-Zip to create a SFX archive of it
> SetDatablockOptimize on
> SetCompress off
>@@ -363,16 +382,22 @@ Section "-Application" APP_IDX
>     ${If} $AddDesktopSC == 1
>     ${OrIf} $AddStartMenuSC == 1
>       WriteRegDWORD HKLM "$0" "IconsVisible" 1
>     ${Else}
>       WriteRegDWORD HKLM "$0" "IconsVisible" 0
>     ${EndIf}
>   ${EndIf}
> 
>+  ${IF} $InstallMaintenanceService == 1
>+    ; The user wants to install the maintenance service, so execute
>+    ; the pre-packaged maintenance service installer. /S for silent.
>+    nsExec::Exec '"$INSTDIR\maintenanceservice_installer\maintenanceservice_installer.exe" /S' 
note: I think this will end up being different than this but I'll call it out in the review of the new install code.

>@@ -787,16 +812,53 @@ Function leaveShortcuts
>     ${MUI_INSTALLOPTIONS_READ} $AddQuickLaunchSC "shortcuts.ini" "Field 4" "State"
>   ${EndUnless}
> 
>   ${If} $InstallType == ${INSTALLTYPE_CUSTOM}
>     Call CheckExistingInstall
>   ${EndIf}
> FunctionEnd
> 
>+Function preComponents
>+  ; Don't show the custom components page if the
>+  ; user is not an admin
>+  Call IsUserAdmin
>+  Pop $R9
>+  ${If} $R9 != "true"
>+    Abort
>+  ${EndIf}
>+
>+  ; If the service already exists, don't show this page
>+  ; We will always install again (which will upgrade)
>+  ; as long as the user is admin
>+  ${Unicode2Ansi} "MozillaMaintenance" $R8
>+  SimpleSC::ExistsService "$R8"
Instead of using the plugin couldn't you just check for the existence of the binary since we require it to be installed into a specific location under %ProgramFiles% or %ProgramFiles(x86)%? It would require two checks but it would remove the need for the plugin and the Unicode2Ansi or recompiling the plugin as x64.

>diff --git a/browser/installer/windows/nsis/uninstaller.nsi b/browser/installer/windows/nsis/uninstaller.nsi
>--- a/browser/installer/windows/nsis/uninstaller.nsi
>+++ b/browser/installer/windows/nsis/uninstaller.nsi
>@@ -14,16 +14,17 @@
> # The Original Code is the Mozilla Installer code.
> #
> # The Initial Developer of the Original Code is Mozilla Foundation
> # Portions created by the Initial Developer are Copyright (C) 2006
> # the Initial Developer. All Rights Reserved.
> #
> # Contributor(s):
> #  Robert Strong <robert.bugzilla@gmail.com>
>+#  Brian R. Bondy <netzen@gmail.com>
> #
> # Alternatively, the contents of this file may be used under the terms of
> # either the GNU General Public License Version 2 or later (the "GPL"), or
> # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
> # in which case the provisions of the GPL or the LGPL are applicable instead
> # of those above. If you wish to allow use of your version of this file only
> # under the terms of either the GPL or the LGPL, and not to allow others to
> # use your version of this file under the terms of the MPL, indicate your
>@@ -155,26 +156,28 @@ ShowUnInstDetails nevershow
> !define MUI_WELCOMEPAGE_TITLE_3LINES
> !define MUI_HEADERIMAGE
> !define MUI_HEADERIMAGE_RIGHT
> !define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
> 
> ; Use a right to left header image when the language is right to left
> !ifdef ${AB_CD}_rtl
> !define MUI_HEADERIMAGE_BITMAP_RTL wizHeaderRTL.bmp
>+!define MUI_HEADERIMAGE_UNBITMAP_RTL wizHeaderRTL.bmp
> !else
> !define MUI_HEADERIMAGE_BITMAP wizHeader.bmp
>+!define MUI_HEADERIMAGE_UNBITMAP wizHeader.bmp
> !endif
It uses the correct header anyway due to the custom branding code. Since we don't display the installer ui for this code could you check if it does the right thing with MUI_HEADERIMAGE_BITMAP and MUI_HEADERIMAGE_BITMAP_RTL undefined and that the default headers aren't included then?

> /**
>  * Uninstall Pages
>  */
> ; Welcome Page
>-!define MUI_PAGE_CUSTOMFUNCTION_PRE un.preWelcome
>-!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.leaveWelcome
>+;!define MUI_PAGE_CUSTOMFUNCTION_PRE un.preWelcome
>+;!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.leaveWelcome
These are needed so the UI can use custom branding.

> !insertmacro MUI_UNPAGE_WELCOME
> 
> ; Custom Uninstall Confirm Page
> UninstPage custom un.preConfirm un.leaveConfirm
> 
> ; Remove Files Page
> !insertmacro MUI_UNPAGE_INSTFILES
> 
>diff --git a/browser/locales/en-US/installer/custom.properties b/browser/locales/en-US/installer/custom.properties
>--- a/browser/locales/en-US/installer/custom.properties
>+++ b/browser/locales/en-US/installer/custom.properties
>@@ -52,28 +52,32 @@
> 
> REG_APP_DESC=$BrandShortName delivers safe, easy web browsing. A familiar user interface, enhanced security features including protection from online identity theft, and integrated search let you get the most out of the web.
> CONTEXT_OPTIONS=$BrandShortName &Options
> CONTEXT_SAFE_MODE=$BrandShortName &Safe Mode
> OPTIONS_PAGE_TITLE=Setup Type
> OPTIONS_PAGE_SUBTITLE=Choose setup options
> SHORTCUTS_PAGE_TITLE=Set Up Shortcuts
> SHORTCUTS_PAGE_SUBTITLE=Create Program Icons
>+COMPONENTS_PAGE_TITLE=Set Up Optional Components
>+COMPONENTS_PAGE_SUBTITLE=Optional Recommended Components
> SUMMARY_PAGE_TITLE=Summary
> SUMMARY_PAGE_SUBTITLE=Ready to start installing $BrandShortName
> SUMMARY_INSTALLED_TO=$BrandShortName will be installed to the following location:
> SUMMARY_REBOOT_REQUIRED_INSTALL=A restart of your computer may be required to complete the installation.
> SUMMARY_REBOOT_REQUIRED_UNINSTALL=A restart of your computer may be required to complete the uninstall.
> SUMMARY_TAKE_DEFAULTS=U&se $BrandShortName as my default web browser
> SUMMARY_INSTALL_CLICK=Click Install to continue.
> SUMMARY_UPGRADE_CLICK=Click Upgrade to continue.
> SURVEY_TEXT=&Tell us what you thought of $BrandShortName
> LAUNCH_TEXT=&Launch $BrandShortName now
> CREATE_ICONS_DESC=Create icons for $BrandShortName:
>+OPTIONAL_COMPONENTS_DESC=The Maintenance Service will allow you to update $BrandShortName silently in the background.
> ICONS_DESKTOP=On my &Desktop
>+MAINTENANCE_SERVICE_CHECKBOX_DESC=Install &Maintenance Service
Just put OPTIONAL_COMPONENTS_DESC and MAINTENANCE_SERVICE_CHECKBOX_DESC directly following COMPONENTS_PAGE_SUBTITLE.

Please get faaborg or limi to approve the text.

Overall looks good and this should be a quick review after it is resubmitted
Attachment #568688 - Flags: review?(robert.bugzilla) → review-
Comment on attachment 568688 [details] [diff] [review]
Patch 10 - Shared installer code and service upgrade

That was meant for patch 9 :/
Attachment #568688 - Flags: review- → review?(robert.bugzilla)
Attachment #569109 - Flags: review?(robert.bugzilla) → review-
Comment on attachment 568688 [details] [diff] [review]
Patch 10 - Shared installer code and service upgrade

>diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh
>--- a/browser/installer/windows/nsis/shared.nsh
>+++ b/browser/installer/windows/nsis/shared.nsh
>@@ -14,32 +14,39 @@
>...
> !macro PostUpdate
>+
>+  ; If a service already exists, the command line parameter will stop the
>+  ; service and only install itself if it is newer than the already installed
>+  ; service.
>+  nsExec::Exec '"$INSTDIR\maintenanceservice_installer\maintenanceservice_installer.exe" /S /Upgrade' 
note: I think this will end up being different than this but I'll call it out in the review of the new install code.

>@@ -935,19 +942,57 @@
>   Push "nspr4.dll"
>   Push "nssdbm3.dll"
>   Push "mozsqlite3.dll"
>   Push "xpcom.dll"
>   Push "crashreporter.exe"
>   Push "updater.exe"
>   Push "${FileMainEXE}"
> !macroend
>+
>+!define Unicode2Ansi "!insertmacro Unicode2Ansi"
>+!macro Unicode2Ansi String outVar
>+  System::Call 'kernel32::WideCharToMultiByte(i 0, i 0, w "${String}", i -1, t .s, i ${NSIS_MAX_STRLEN}, i 0, i 0) i'
>+  Pop "${outVar}"
>+!macroend
Won't be needed if the file check approach is used.

>+
>+; Copied from: http://nsis.sourceforge.net/IsUserAdmin
>+Function IsUserAdmin
>+  Push $R0
>+  Push $R1
>+  Push $R2
>+ 
>+  ClearErrors
>+  UserInfo::GetName
>+  IfErrors Win9x
>+  Pop $R1
>+  UserInfo::GetAccountType
>+  Pop $R2
>+ 
>+  StrCmp $R2 "Admin" 0 Continue
>+  StrCpy $R0 "true"
>+  Goto Done
>+ 
>+  Continue:
>+
>+  StrCmp $R2 "" Win9x
>+  StrCpy $R0 "false"
>+  Goto Done
>+ 
>+  Win9x:
>+  StrCpy $R0 "true"
>+ 
>+  Done:
>+  Pop $R2
>+  Pop $R1
>+  Exch $R0
>+FunctionEnd
>+
I'd prefer if IsUserAdmin was turned into a macro and added to common.nsh so all apps can use it

Should be a quick review with those changes
Attachment #568688 - Flags: review?(robert.bugzilla) → review-
Comment on attachment 568689 [details] [diff] [review]
Patch 11 - New maintenance service installer

>diff --git a/browser/installer/windows/nsis/maintenanceservice_installer.nsi b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
It seems to me that this could always run silently and thereby simplify the code quite a lot.

Also, it would lessen the size of the overall installer if this installer didn't include the files inside of it. Instead, you can just install the files in the root of the installation directory and copy them to the destination directory.

Are you ok with doing it that way?
Regarding "always run silently", I am referring to the install process. I haven't looked at the uninstall process yet.
Comment on attachment 568689 [details] [diff] [review]
Patch 11 - New maintenance service installer

This is dependent on whether the file check is sufficient or the plugin is actually needed. Removing review until that is answered.
Attachment #568689 - Flags: review?(robert.bugzilla)
Comment on attachment 568690 [details] [diff] [review]
Patch 12 - Simple SC plugin inclusion

Too many reviews! :)

This is the patch that is dependent on whether the file check is sufficient or the plugin is actually needed. Removing review until that is answered.
Attachment #568690 - Flags: review?(robert.bugzilla)
Comment on attachment 568691 [details] [diff] [review]
Patch 13 - Moving the update check after init

I'm overall ok with this though it does scare me a bit since there may be unintended consequences. Does the profile manager display twice when it is set to be displayed for example?

I'm going to test this out before r+ing and we might want to list the behavior changes so they are understood.
> I'm overall ok with this though it does scare me a bit

Me too, I hate moving things around at init.  I haven't sen any issues yet though.

> Does the profile manager display twice when it is set to be displayed for example?

Do you mean just: 
firefox.exe -ProfileManager ?

If that's what you mean no it only displays once.  There isn't a second init call it's just a move of the update call a little lower in the init.

Or did you mean when there is an update applied?
When an update is applied and all cases where the profile manager is displayed. I suspect it does in that case and we added code elsewhere to cover that case.
Patch 13 - Moving the update check after init.

OK I tested it and you're absolutely right.

When you start Firefox -ProfileManager when there is a pending update, it will show the profile selection screen.
You select the profile you want, then it does the update.

After the update, the service then relaunches with -ProfileManager but the profile selection screen doesn't display. 
So there must be some other code taking care of that already even know the command line was passed.

If there are some other special cases like this that are problems, we can filter out those command line parameters similar to how Patch 16 works.
> It isn't clear to me whether it is better to use a common key for 
> all cert attributes for all installs or to have each install store 
> the cert attributes in an install specific key. I am leaning install 
> specific keys since another app should not be able to provide cert 
> attributes that are then used by another app. What do you think?

The way it currently works is that you can have any amount of subfolders under the main Certs key.  Each subkey has the attributes which are allowed.  How do you envision appliction specific locations working? How does the service know where to look?
We could use the install path or a hash of the install path in the registry.
Implemented review comments.

> Ideally, we would allow the application to specify what cert attributes to check 
> though I think this is fine at least for now.

This is already supported just leave the fields blank to skip that check.
Attachment #568679 - Attachment is obsolete: true
Attachment #569895 - Flags: review?(robert.bugzilla)
Same as last patch but updated function name.
Attachment #569683 - Attachment is obsolete: true
Attachment #569896 - Flags: review?(robert.bugzilla)
Attachment #569683 - Flags: review?(robert.bugzilla)
Same as original patch, just has updated function name.
Attachment #569679 - Attachment is obsolete: true
Attachment #569897 - Flags: review?(robert.bugzilla)
Attachment #569679 - Flags: review?(robert.bugzilla)
>+  // Note we construct the 'service work' meta object with a ma extension
>+  // When we want the service to start processing it we simply rename it to
>+  // have a .mz extension.  This ensures that the service will never try to
>+  // process a partial update work meta file. 
> Would it make sense to create the file in a different directory (temp?) and when 
> it is ready move it to the directory?

I'd prefer to keep it in the same directory to avoid complications.  For example if the temp directory was in a different volume and you move the file across volumes you will lose some informatin like security descriptors and it might be of a different fs type hence losing even more info. Also you'd need to specify the flag MOVEFILE_COPY_ALLOWED in that case which uses more space and could fail due to really low disk space situations.  On a different volume w/ MoveFileEx copying it might even cause the service to process a partial work item file.
(In reply to Brian R. Bondy [:bbondy] from comment #198)
> > Would it make sense to create the file in a different directory (temp?) and when it is ready move it to the directory?
> 
> I'd prefer to keep it in the same directory to avoid complications.  For
> example if the temp directory was in a different volume and you move the
> file across volumes you will lose some informatin like security descriptors
> and it might be of a different fs type hence losing even more info. Also
> you'd need to specify the flag MOVEFILE_COPY_ALLOWED in that case which uses
> more space and could fail due to really low disk space situations.  On a
> different volume w/ MoveFileEx copying it might even cause the service to
> process a partial work item file.
Brian, are you aware of bug 307181?
> Brian, are you aware of bug 307181?

Yup this MoveFileEx call is admittedly much smaller of a potential problem than that but I was just explaining why I chose to keep it in the same directory while programming it.  I do plan on doing a feedback review on bug 307181 though so thanks for checking to make sure.
Review comments implemented.
Regarding the CreateDirectory / owner issue you brought up I'll add the directory creations as well to the installer to avoid this problem.
Attachment #569605 - Attachment is obsolete: true
Attachment #569977 - Flags: review?(robert.bugzilla)
>+  SimpleSC::ExistsService "$R8"
> Instead of using the plugin couldn't you just check for the existence of the binary since 
> we require it to be installed into a specific location under %ProgramFiles% or %ProgramFiles(x86)%? 
> It would require two checks but it would remove the need for the plugin and the Unicode2Ansi or recompiling the plugin as x64.

I think even for x64 compilations NSIS always produces x86 installers.
There is no real need security wise or coding logic wise that requires the service to be in one of those 2 locations.
Also in case someone manually uninstalls the service but the file exists I think the check for existence of the service itself is best.

Also I had planned to change this eventually but for any future version after this first release, we'll need to use Simple SC's stop service as well before installing the service.
It's not a problem right now because nspr4.dll will never be new (since it's a first install) but there is potential right now for it being in use if a user manually started the service.

For now I'll go ahead with keeping Simple SC and also adding the Stop Service call.
But please let me know if you are strongly for not including this plug-in, then I will rethink the situation and make a different patch.
(In reply to Brian R. Bondy [:bbondy] from comment #202)
> >+  SimpleSC::ExistsService "$R8"
> > Instead of using the plugin couldn't you just check for the existence of the binary since 
> > we require it to be installed into a specific location under %ProgramFiles% or %ProgramFiles(x86)%? 
> > It would require two checks but it would remove the need for the plugin and the Unicode2Ansi or recompiling the plugin as x64.
> 
> I think even for x64 compilations NSIS always produces x86 installers.
> There is no real need security wise or coding logic wise that requires the
> service to be in one of those 2 locations.
> Also in case someone manually uninstalls the service but the file exists I
> think the check for existence of the service itself is best.
> 
> Also I had planned to change this eventually but for any future version
> after this first release, we'll need to use Simple SC's stop service as well
> before installing the service.
> It's not a problem right now because nspr4.dll will never be new (since it's
> a first install) but there is potential right now for it being in use if a
> user manually started the service.
> 
> For now I'll go ahead with keeping Simple SC and also adding the Stop
> Service call.
> But please let me know if you are strongly for not including this plug-in,
> then I will rethink the situation and make a different patch.
My main concern is that this will need to work with nsis 2.33u (I don't recall if it supports ANSI plugins) since that is what is deployed to the build machines since bug 594474 hasn't been fixed yet.
> My main concern is that this will need to work with nsis 2.33u (I don't recall if > it supports ANSI plugins) since that is what is deployed to the build machines > since bug 594474 hasn't been fixed yet.

Would you be OK if I make SimpleSC part of the build process and make it unicode?
That won't work (at least not easily) because localizers repackage builds for locales at which time we rebuild the installer. You could convert it to unicode and checkin the binary as is done here:
http://mxr.mozilla.org/mozilla-central/source/other-licenses/nsis/
Comment on attachment 569681 [details] [diff] [review]
Patch 15 - UAC helper functions.

>diff --git a/toolkit/components/maintenanceservice/Makefile.in b/toolkit/components/maintenanceservice/Makefile.in
>--- a/toolkit/components/maintenanceservice/Makefile.in
>+++ b/toolkit/components/maintenanceservice/Makefile.in
>@@ -44,16 +44,17 @@ include $(DEPTH)/config/autoconf.mk
> 
> CPPSRCS = \
>   maintenanceservice.cpp \
> 	serviceinstall.cpp \
> 	workmonitor.cpp \
> 	certificatecheck.cpp \
> 	servicebase.cpp \
> 	registrycertificates.cpp \
>+	uachelper.cpp \
spaces are prefered

>diff --git a/toolkit/components/maintenanceservice/uachelper.cpp b/toolkit/components/maintenanceservice/uachelper.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/uachelper.cpp
>@@ -0,0 +1,157 @@
>+/* ***** BEGIN LICENSE BLOCK *****
>+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
>+ *
>+ * The contents of this file are subject to the Mozilla Public License Version
>+ * 1.1 (the "License"); you may not use this file except in compliance with
>+ * the License. You may obtain a copy of the License at
>+ * http://www.mozilla.org/MPL/
>+ *
>+ * Software distributed under the License is distributed on an "AS IS" basis,
>+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
>+ * for the specific language governing rights and limitations under the
>+ * License.
>+ *
>+ * The Original Code is UAC helper functions.
Maintenance service UAC helper functions

>+ *
>+ * The Initial Developer of the Original Code is
>+ * Mozilla Foundation.
>+ * Portions created by the Initial Developer are Copyright (C) 2011
>+ * the Initial Developer. All Rights Reserved.
>+ *
>+ * Contributor(s):
>+ *   Brian R. Bondy <netzen@gmail.com>
>+ *
>+ * Alternatively, the contents of this file may be used under the terms of
>+ * either the GNU General Public License Version 2 or later (the "GPL"), or
>+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
>+ * in which case the provisions of the GPL or the LGPL are applicable instead
>+ * of those above. If you wish to allow use of your version of this file only
>+ * under the terms of either the GPL or the LGPL, and not to allow others to
>+ * use your version of this file under the terms of the MPL, indicate your
>+ * decision by deleting the provisions above and replace them with the notice
>+ * and other provisions required by the GPL or the LGPL. If you do not delete
>+ * the provisions above, a recipient may use your version of this file under
>+ * the terms of any one of the MPL, the GPL or the LGPL.
>+ *
>+ * ***** END LICENSE BLOCK ***** */
>+
>+#include <windows.h>
>+#include "uachelper.h"
>+
>+typedef BOOL (WINAPI *LPWTSQueryUserToken)(ULONG, PHANDLE);
>+
>+// static
>+bool UACHelper::IsVistaOrLater() 
as mentioned earlier the function name should be on its own line here and elsewhere

>+{
>+  // Check if we are running Vista or later.
>+  OSVERSIONINFO osInfo;
>+  osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
>+  return GetVersionEx(&osInfo) && osInfo.dwMajorVersion >= 6;
>+}
>+
>+// static
>+HANDLE UACHelper::OpenUserToken(DWORD sessionID)
>+{
>+  HMODULE module = LoadLibraryW(L"wtsapi32.dll");
>+  HANDLE token = NULL;
>+  LPWTSQueryUserToken wtsQueryUserToken = (LPWTSQueryUserToken)GetProcAddress(module, "WTSQueryUserToken");
>+  if (wtsQueryUserToken) {
>+    wtsQueryUserToken(sessionID, &token);
>+  }
>+  FreeModule(module);
>+  return token;
>+}
>+
>+// static
>+HANDLE UACHelper::OpenLinkedToken(HANDLE token) 
>+{
>+  // Magic below...
>+  // UAC creates 2 tokens.  One is the restricted token which we have.
>+  // the other is the UAC elevated one. Since we are running as a service
>+  // as the system account we have access to both.
>+  TOKEN_LINKED_TOKEN tlt;
>+  HANDLE hNewLinkedToken = NULL;
>+  DWORD len;
>+  if(::GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, 
>+    &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) {
nit: alignment

>+      token = tlt.LinkedToken;
>+      hNewLinkedToken = token;
>+  }
>+  return hNewLinkedToken;
>+}
>+
>+// static
>+bool UACHelper::IsUserAdmin(HANDLE token, BOOL &isAdmin)
>+{
>+  bool success = false;
>+  SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
>+  PSID administratorsGroup; 
>+  if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
>+                               DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
>+                               &administratorsGroup)) {
>+    if (CheckTokenMembership(token, administratorsGroup, &isAdmin)) {
>+      success = true;
nit I'm fine with either TRUE / FALSE or true / false for the standalone app... just be consistent across the files.

>+    } 
>+    FreeSid(administratorsGroup); 
>+  }
>+  return success;
>+}
>+
>+// static
>+bool UACHelper::GetElevationType(HANDLE token, UserType &userType)
>+{
>+  if (!IsVistaOrLater()) {
>+    BOOL isAdmin;
>+    if (!IsUserAdmin(token, isAdmin)) {
>+      return false;
>+    }
>+
>+    if (isAdmin) {
>+      userType = AdministratorUACIsOff;
>+    } else {
>+      userType = LimitedUser;
>+    }
>+    return true;
>+  }
>+
>+  // TokenElevationTypeDefault: The token does not have a linked token 
>+  // This happens when the user is a limited account or UAC is off.
>+  // TokenElevationTypeFull: The token is an elevated token.
>+  // TokenElevationTypeLimited: The token is a limited token.
>+  DWORD returnLength = 0;
>+  TOKEN_ELEVATION_TYPE elevationType;
>+  if (!::GetTokenInformation(token, TokenElevationType,
>+    &elevationType, sizeof(elevationType), &returnLength)) {
nit: alignment

>+      return false;
>+  }
>+
>+  switch (elevationType) {
nit: switch alignment as mentioned earlier

>+  case TokenElevationTypeFull:
>+    userType = AdministratorElevated;
>+    break;
>+  case TokenElevationTypeLimited:
>+    userType = AdministratorUnelevated;
>+    break;
>+  case TokenElevationTypeDefault: 
>+    {
>+      // TokenElevationTypeDefault is returned when either the user is a 
>+      // limited account or the user has UAC offa limited user or UAC is off
>+      BOOL isAdmin;
>+      if (!IsUserAdmin(token, isAdmin)) {
>+        return false;
>+      }
>+
>+      if (isAdmin) {
>+        userType = AdministratorUACIsOff;
Kind of prefer just Administrator but I am fine with this.

>+      } else {
>+        userType = LimitedUser;
>+      }
>+    }
>+    break;
>+  default:
>+    return false;
>+  }
>+
>+  // If we had an error we would have returned already
>+  return true;
>+}
Attachment #569681 - Flags: review?(robert.bugzilla) → review+
Comment on attachment 568691 [details] [diff] [review]
Patch 13 - Moving the update check after init

I don't see any other way we can accomplish this reasonably... we'll just need to keep a close eye on this.
Attachment #568691 - Flags: review?(robert.bugzilla) → review+
Comment on attachment 568693 [details] [diff] [review]
Patch 14 - Build process

>diff --git a/browser/Makefile.in b/browser/Makefile.in
>--- a/browser/Makefile.in
>+++ b/browser/Makefile.in
>@@ -65,11 +65,11 @@ include $(topsrcdir)/config/rules.mk
> 
> ifeq ($(OS_ARCH),WINNT)
> ifdef MOZ_INSTALLER
> 
> # For Windows build the uninstaller during the application build since the
> # uninstaller is included with the application for mar file generation.
> libs::
> 	$(MAKE) -C installer/windows uninstaller
>-
>+	$(MAKE) -C installer/windows maintenanceservice_installer
> endif
> endif
>diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
>--- a/browser/installer/package-manifest.in
>+++ b/browser/installer/package-manifest.in
>@@ -28,16 +28,17 @@
> @BINPATH@/defaults/profile/mimeTypes.rdf
> @BINPATH@/defaults/profile/chrome/*
> @BINPATH@/update.locale
> @BINPATH@/updater.ini
> @BINPATH@/dictionaries/*
> @BINPATH@/hyphenation/*
> #ifdef XP_WIN32
> @BINPATH@/uninstall/helper.exe
>+@BINPATH@/maintenanceservice_installer/maintenanceservice_installer.exe
Per phone conversation, no maintenanceservice_installer directory and it appears to be proper below.

> #endif
> 
> [xpcom]
> @BINPATH@/dependentlibs.list
> #ifndef MOZ_STATIC_JS
> @BINPATH@/@DLL_PREFIX@mozjs@DLL_SUFFIX@
> #endif
> @BINPATH@/@DLL_PREFIX@plc4@DLL_SUFFIX@
>@@ -522,16 +523,22 @@ bin/libfreebl_32int64_3.so
> ; [Updater]
> ;
> #ifdef XP_MACOSX
> @BINPATH@/updater.app/
> #else
> @BINPATH@/updater@BIN_SUFFIX@
> #endif
> 
>+; [MaintenanceService]
>+;
>+#ifdef XP_WIN32
>+@BINPATH@/maintenanceservice.exe
>+#endif
>+

>diff --git a/browser/installer/windows/Makefile.in b/browser/installer/windows/Makefile.in
>--- a/browser/installer/windows/Makefile.in
>+++ b/browser/installer/windows/Makefile.in
>...
>@@ -70,16 +71,21 @@ BRANDING_FILES = \
> 
> DEFINES += \
> 	-DAB_CD=$(AB_CD) \
> 	-DMOZ_APP_NAME=$(MOZ_APP_NAME) \
> 	-DMOZ_APP_DISPLAYNAME=${MOZ_APP_DISPLAYNAME} \
> 	-DMOZILLA_VERSION=${MOZILLA_VERSION} \
> 	$(NULL)
> 
>+MAINTENANCESERVICE_FILES = \
>+	maintenanceservice.exe \
>+	nspr4.dll \
>+	$(NULL)
Needs to be updated per discussion

>@@ -99,16 +105,30 @@ uninstaller::
> 	$(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR)
> 	$(INSTALL) $(addprefix $(DIST)/branding/,$(BRANDING_FILES)) $(CONFIG_DIR)
> 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py -Fsubstitution $(DEFINES) $(ACDEFINES) \
> 	  $(srcdir)/nsis/defines.nsi.in > $(CONFIG_DIR)/defines.nsi
> 	$(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
> 	  --preprocess-locale $(topsrcdir) \
> 	  $(PPL_LOCALE_ARGS) $(AB_CD) $(CONFIG_DIR)
> 
>+# For building the maintenanceservice installer
>+maintenanceservice_installer::
>+	$(RM) -r $(CONFIG_DIR)
>+	$(MKDIR) $(CONFIG_DIR)
>+	$(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR)
>+	$(INSTALL) $(addprefix $(DIST)/branding/,$(BRANDING_FILES)) $(CONFIG_DIR)
>+	$(INSTALL) $(addprefix $(DIST)/bin/,$(MAINTENANCESERVICE_FILES)) $(CONFIG_DIR)
>+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py -Fsubstitution $(DEFINES) $(ACDEFINES) \
>+	  $(srcdir)/nsis/defines.nsi.in > $(CONFIG_DIR)/defines.nsi
>+	$(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
>+	  --preprocess-locale $(topsrcdir) \
>+	  $(PPL_LOCALE_ARGS) $(AB_CD) $(CONFIG_DIR)
Needs to be updated per discussion

>diff --git a/mobile/installer/package-manifest.in b/mobile/installer/package-manifest.in
>--- a/mobile/installer/package-manifest.in
>+++ b/mobile/installer/package-manifest.in
>@@ -30,16 +30,17 @@
> #ifdef MOZ_UPDATER
> @BINPATH@/update.locale
> @BINPATH@/updater.ini
> #endif
> @BINPATH@/dictionaries/*
> @BINPATH@/hyphenation/*
> #ifdef XP_WIN32
> @BINPATH@/uninstall/helper.exe
>+@BINPATH@/maintenanceservice_installer/maintenanceservice_installer.exe
Mobile?

>diff --git a/toolkit/components/maintenanceservice/Makefile.in b/toolkit/components/maintenanceservice/Makefile.in
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/Makefile.in
>@@ -0,0 +1,105 @@
>+# ***** BEGIN LICENSE BLOCK *****
>+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
>+#
>+# The contents of this file are subject to the Mozilla Public License Version
>+# 1.1 (the "License"); you may not use this file except in compliance with
>+# the License. You may obtain a copy of the License at
>+# http://www.mozilla.org/MPL/
>+#
>+# Software distributed under the License is distributed on an "AS IS" basis,
>+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
>+# for the specific language governing rights and limitations under the
>+# License.
>+#
>+# The Original Code is Application Update Service.
Maintenance service.

>+include $(DEPTH)/config/autoconf.mk
>+
>+CPPSRCS = \
>+  maintenanceservice.cpp \
>+	serviceinstall.cpp \
>+	workmonitor.cpp \
>+	certificatecheck.cpp \
>+	servicebase.cpp \
>+	registrycertificates.cpp \
>+  $(NULL)
Just use spaces... iirc this is per Ted and / or Kyle

>+
>+ifneq ($(OS_TARGET),Android)
>+PROGRAM = maintenanceservice$(BIN_SUFFIX)
>+DIST_PROGRAM = maintenanceservice$(BIN_SUFFIX)
>+
>+# Don't link the maintenanceservice against libmozutils. See bug 687139
>+MOZ_UTILS_LDFLAGS =
>+MOZ_UTILS_PROGRAM_LDFLAGS =
>+endif
>+
>+LIBS += \
>+  $(DEPTH)/modules/libmar/src/$(LIB_PREFIX)mar.$(LIB_SUFFIX) \
>+  $(BZ2_LIBS) \
>+  $(NSPR_LIBS) \
>+  $(NULL)
>+
>+ifeq ($(OS_ARCH),WINNT)
>+USE_STATIC_LIBS = 1
>+HAVE_PROGRESSUI = 1
>+RCINCLUDE = maintenanceservice.rc
>+
>+OS_LIBS += $(call EXPAND_LIBNAME,comctl32 ws2_32 shell32)
>+DEFINES += -DUNICODE -D_UNICODE
>+ifndef GNU_CC
>+RCFLAGS += -I$(srcdir)
>+else
>+RCFLAGS += --include-dir $(srcdir)
>+endif
>+
>+endif
>+
>+ifndef MOZ_WINCONSOLE
>+ifdef MOZ_DEBUG
>+MOZ_WINCONSOLE = 1
>+else
>+MOZ_WINCONSOLE = 0
>+endif
>+endif
>+
>+include $(topsrcdir)/config/rules.mk
>+
>+DEFINES += -DNS_NO_XPCOM
>+
>+ifdef _MSC_VER
>+WIN32_EXE_LDFLAGS += -ENTRY:wmainCRTStartup
>+endif
>+
>+ifeq (,$(filter-out WINNT,$(OS_ARCH)))
>+# Pick up nsWindowsRestart.cpp
>+LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre
>+endif
>+
>+CXXFLAGS += $(BZ2_CFLAGS)
This looks like it was copied and has a bunch of cruft
Comment on attachment 568693 [details] [diff] [review]
Patch 14 - Build process

>diff --git a/toolkit/locales/l10n.mk b/toolkit/locales/l10n.mk
>--- a/toolkit/locales/l10n.mk
>+++ b/toolkit/locales/l10n.mk
>@@ -139,16 +139,18 @@ endif
> repackage-zip: UNPACKAGE="$(ZIP_IN)"
> repackage-zip:  libs-$(AB_CD)
> # Adjust jar logs with the new locale (can't use sed -i because of bug 373784)
> 	mkdir -p $(JARLOG_DIR_AB_CD)
> 	-cp -r $(JARLOG_DIR)/en-US/*.jar.log $(JARLOG_DIR_AB_CD)
> 	-$(PERL) -pi.old -e "s/en-US/$(AB_CD)/g" $(JARLOG_DIR_AB_CD)/*.jar.log
> # call a hook for apps to put their uninstall helper.exe into the package
> 	$(UNINSTALLER_PACKAGE_HOOK)
>+# call a hook for apps to put their maintenance service into the package
>+	$(MAINTENANCESERVICE_PACKAGE_HOOK)
Not all apps will build the maintenance service.

>diff --git a/toolkit/mozapps/installer/windows/nsis/makensis.mk b/toolkit/mozapps/installer/windows/nsis/makensis.mk
>--- a/toolkit/mozapps/installer/windows/nsis/makensis.mk
>+++ b/toolkit/mozapps/installer/windows/nsis/makensis.mk
>@@ -55,25 +55,34 @@ TOOLKIT_NSIS_FILES = \
> 
> CUSTOM_NSIS_PLUGINS = \
> 	AccessControl.dll \
> 	AppAssocReg.dll \
> 	ApplicationID.dll \
> 	InvokeShellVerb.dll \
> 	ShellLink.dll \
> 	UAC.dll \
>+	SimpleSC.dll \
> 	$(NULL)
> 
>+MAINTENANCESERVICE_FILES = \
>+	maintenanceservice.exe \
>+	nspr4.dll \
>+	$(NULL)
Per phone discussion, just use the files in the root of the app dir

>+
>+
> $(CONFIG_DIR)/setup.exe::
> 	$(INSTALL) $(addprefix $(MOZILLA_DIR)/toolkit/mozapps/installer/windows/nsis/,$(TOOLKIT_NSIS_FILES)) $(CONFIG_DIR)
> 	$(INSTALL) $(addprefix $(MOZILLA_DIR)/other-licenses/nsis/Plugins/,$(CUSTOM_NSIS_PLUGINS)) $(CONFIG_DIR)
> 	cd $(CONFIG_DIR) && $(MAKENSISU) installer.nsi
>-# Support for building the uninstaller when repackaging locales
>+# Support for building the uninstaller and maintenance service when repackaging locales
> ifeq ($(CONFIG_DIR),l10ngen)
>+	$(INSTALL) $(addprefix $(DIST)/bin/,$(MAINTENANCESERVICE_FILES)) $(CONFIG_DIR)
Should not be necessary. Also, you can't install it before building it which happens after this

> 	cd $(CONFIG_DIR) && $(MAKENSISU) uninstaller.nsi
>+	cd $(CONFIG_DIR) && $(MAKENSISU) maintenanceservice_installer.nsi
This should only be called when the app has maintenanceservice_installer.nsi

> endif
> 
> $(CONFIG_DIR)/7zSD.sfx:
> 	$(CYGWIN_WRAPPER) upx --best -o $(CONFIG_DIR)/7zSD.sfx $(SFX_MODULE)
> 
> installer::
> 	$(INSTALL) $(CONFIG_DIR)/setup.exe $(DEPTH)/installer-stage
> 	cd $(DEPTH)/installer-stage && $(CYGWIN_WRAPPER) 7z a -r -t7z $(ABS_CONFIG_DIR)/app.7z -mx -m0=BCJ2 -m1=LZMA:d24 -m2=LZMA:d19 -m3=LZMA:d19  -mb0:1 -mb0s1:2 -mb0s2:3
>@@ -85,8 +94,17 @@ installer::
> # For building the uninstaller during the application build so it can be
> # included for mar file generation.
> uninstaller::
> 	$(INSTALL) $(addprefix $(MOZILLA_DIR)/toolkit/mozapps/installer/windows/nsis/,$(TOOLKIT_NSIS_FILES)) $(CONFIG_DIR)
> 	$(INSTALL) $(addprefix $(MOZILLA_DIR)/other-licenses/nsis/Plugins/,$(CUSTOM_NSIS_PLUGINS)) $(CONFIG_DIR)
> 	cd $(CONFIG_DIR) && $(MAKENSISU) uninstaller.nsi
> 	$(NSINSTALL) -D $(DIST)/bin/uninstall
> 	cp $(CONFIG_DIR)/helper.exe $(DIST)/bin/uninstall
>+
>+# For building the maintenanceservice installer during the application build
>+maintenanceservice_installer::
>+	$(INSTALL) $(addprefix $(MOZILLA_DIR)/toolkit/mozapps/installer/windows/nsis/,$(TOOLKIT_NSIS_FILES)) $(CONFIG_DIR)
>+	$(INSTALL) $(addprefix $(MOZILLA_DIR)/other-licenses/nsis/Plugins/,$(CUSTOM_NSIS_PLUGINS)) $(CONFIG_DIR)
>+	$(INSTALL) $(addprefix $(DIST)/bin/,$(MAINTENANCESERVICE_FILES)) $(CONFIG_DIR)
Should not be necessary

>+	cd $(CONFIG_DIR) && $(MAKENSISU) maintenanceservice_installer.nsi
>+	$(NSINSTALL) -D $(DIST)/bin/maintenanceservice_installer
>+	cp $(CONFIG_DIR)/maintenanceservice_installer.exe $(DIST)/bin/maintenanceservice_installer
Should be in the root of the app dir

I *think* having a separate target is the right choice so other apps that don't use the service don't try to build it but the $(CONFIG_DIR)/setup.exe target builds maintenanceservice_installer.exe so that will break other apps.
Attachment #568693 - Flags: review?(robert.bugzilla) → review-
Comment on attachment 568689 [details] [diff] [review]
Patch 11 - New maintenance service installer

Needs updating per phone discussion
Attachment #568689 - Flags: review-
Implemented review comments, added javadoc comments and other formatting fixes.  r+'ed myself since it was already r+.  rs, let me know if you have any extra comments though you want me to change.
Attachment #569681 - Attachment is obsolete: true
Attachment #570127 - Flags: review+
Attached patch Patch 1 - Base service code. v3. (obsolete) — Splinter Review
Fixed bool/BOOL consistency and return type of functions on its own line
Attachment #569692 - Attachment is obsolete: true
Attachment #570143 - Flags: review?(robert.bugzilla)
Attachment #569692 - Flags: review?(robert.bugzilla)
Attached patch Patch 2 - Main service logic. v4 (obsolete) — Splinter Review
Fixed bool/BOOL consistency and return type of functions on its own line.
Attachment #569896 - Attachment is obsolete: true
Attachment #570145 - Flags: review?(robert.bugzilla)
Attachment #569896 - Flags: review?(robert.bugzilla)
Fixed bool/BOOL consistency and return type of functions on its own line.
Attachment #569744 - Attachment is obsolete: true
Attachment #570146 - Flags: review?(robert.bugzilla)
Attachment #569744 - Flags: review?(robert.bugzilla)
Comment on attachment 569895 [details] [diff] [review]
Patch 4 - Certificate check code inside service. v2.

Cancelling review on this until I implement changes as per discussion on phone with registry layout (hashing per install path).
Attachment #569895 - Flags: review?(robert.bugzilla)
Fixed return type of functions on its own line.
Attachment #569977 - Attachment is obsolete: true
Attachment #570148 - Flags: review?(robert.bugzilla)
Attachment #569977 - Flags: review?(robert.bugzilla)
Comment on attachment 568950 [details] [diff] [review]
Patch 6 - Firefox preferences update page changes. v2.

As per the review on patch 6 previously:

> As stated in the last patch, I'm not certain this pref will be 
> available when the update is applied during startup.
> minusing until the pref issue is figured out.

re-marking as r? since I think we're going ahead with this method now.
Attachment #568950 - Flags: review- → review?(robert.bugzilla)
Rebased.
Attachment #569897 - Attachment is obsolete: true
Attachment #570153 - Flags: review?(robert.bugzilla)
Attachment #569897 - Flags: review?(robert.bugzilla)
Moved out Makefile.in to patch 14.  Was already r+, remarking.
Attachment #570127 - Attachment is obsolete: true
Attachment #570282 - Flags: review+
Turns out SimpleSC is pascal and hence why no one has tried to port it to Unicode.  I'm going to go ahead and make my own plugin, I've made some before so should only take a couple hours.
Attachment #569110 - Flags: ui-review?(faaborg) → ui-review+
- Removed SimpleSC.
- Created new plugin ServicesHelper.  

This is created from scratch so I'm not sure of the best location in the tree.
It doesn't seem ideal to put it under other-licenses/nsis/Contrib/ but to keep it alongside the other plugins I think that is best.  Let me know if you want me to move it somewhere else.
Attachment #568690 - Attachment is obsolete: true
Attachment #570440 - Flags: review?(robert.bugzilla)
- Removed passing /S to the installer since it is silent now for installation.
- Now use the new plugin ServicesHelper instead of SimpleSC.
- Review comments implemented.
Attachment #569109 - Attachment is obsolete: true
Attachment #570441 - Flags: review?(robert.bugzilla)
Attachment #570440 - Attachment is patch: true
Attachment #570282 - Attachment is patch: true
- Now use the new plugin ServicesHelper.
- Made maintenanceservice installer always silent.
- Fixed up uninstaller to be at par with Firefox uninstaller.
- Review comments implemented.
Attachment #568689 - Attachment is obsolete: true
Attachment #570443 - Flags: review?(robert.bugzilla)
Attached patch Patch 14 - Build process. v2. (obsolete) — Splinter Review
- Review comments implemented
- removed extra folder for maintenanceservice_installer throughout
Attachment #568693 - Attachment is obsolete: true
Attachment #570444 - Flags: review?(robert.bugzilla)
Attachment #569110 - Attachment is obsolete: true
Comment on attachment 567667 [details]
Screenshot: New installer step inside Firefox installer

You previously r+'ed this but rs mentioned he wanted you to specifically r+ the text I came up with.  If you have any other suggestions please let me know.

+COMPONENTS_PAGE_TITLE=Set Up Optional Components
+COMPONENTS_PAGE_SUBTITLE=Optional Recommended Components
+OPTIONAL_COMPONENTS_DESC=The Maintenance Service will allow you to update $BrandShortName silently in the background.
+MAINTENANCE_SERVICE_CHECKBOX_DESC=Install &Maintenance Service
Attachment #567667 - Flags: ui-review+ → ui-review?(faaborg)
Attached patch Patch 14 - Build process. v3. (obsolete) — Splinter Review
Minor change to fix build when only building maintenanceservice_installer target
Attachment #570444 - Attachment is obsolete: true
Attachment #570493 - Flags: review?(robert.bugzilla)
Attachment #570444 - Flags: review?(robert.bugzilla)
Added extra comment for clarity.
Attachment #570441 - Attachment is obsolete: true
Attachment #570494 - Flags: review?(robert.bugzilla)
Attachment #570441 - Flags: review?(robert.bugzilla)
Added code for setting a registry DWORD to indicate that the maintenanceservice was previously installed.  This is to ensure we never re-install on upgrade after the first time instsall.
Attachment #570443 - Attachment is obsolete: true
Attachment #570495 - Flags: review?(robert.bugzilla)
Attachment #570443 - Flags: review?(robert.bugzilla)
- Added code to ensure that the maintenance service installer is launched exactly once for all future upgrades.
- Review comments implemented
Attachment #568688 - Attachment is obsolete: true
Attachment #570496 - Flags: review?(robert.bugzilla)
minor fix.
Attachment #570494 - Attachment is obsolete: true
Attachment #570507 - Flags: review?(robert.bugzilla)
Attachment #570494 - Flags: review?(robert.bugzilla)
Same functionality as before just did some extra refactoring for a callback app function.
Attachment #570153 - Attachment is obsolete: true
Attachment #570517 - Flags: review?(robert.bugzilla)
Attachment #570153 - Flags: review?(robert.bugzilla)
RE and udpater js code for the new update.status value of "pending-no-service".
If this value is set then the service will not be attempted for updating.  
This status is set by the service when there is an error.
Attachment #570518 - Flags: review?(robert.bugzilla)
Now write out the update.status "pending-no-service" code when the service fails. This works very nicely because the callback app is called right after this. 
This means that if the service fails, the user doens't need to start Firefox a second time, it will just look as if the user had a UAC prompt to begin with.
Attachment #570519 - Flags: review?(robert.bugzilla)
As discussed we create the update dir from the maintenanceservice installer now and set the permissions on it with the AccessControl plugin.
Attachment #570522 - Flags: review?(robert.bugzilla)
Existing try tests for the 19 patches looking good, altough I haven't created my own new ones yet:
https://tbpl.mozilla.org/?tree=Try&rev=9d974e9b43d2
Attachment #570595 - Flags: review?(robert.bugzilla)
Added in new NSIS plugin function for getting cert location based on hash.  Also compiled the plugin using VC++ 6.0 so it's a smaller binary.  (I found some old CDs and installed on an XP machine to build it).
Attachment #570440 - Attachment is obsolete: true
Attachment #570596 - Flags: review?(robert.bugzilla)
Attachment #570440 - Flags: review?(robert.bugzilla)
Rebasing (Added in base installer path parameter for registry cert check.)
Attachment #570519 - Attachment is obsolete: true
Attachment #570597 - Flags: review?(robert.bugzilla)
Attachment #570519 - Flags: review?(robert.bugzilla)
No longer use hardcoded registry base path, get it from the unique registry location calculated from base path for update instead.
Attachment #569895 - Attachment is obsolete: true
Attachment #570598 - Flags: review?(robert.bugzilla)
Same as before but removed the setting allowed cert from the maintenance service installer.  It belongs inside the firefox installer and per product installers.
Attachment #570495 - Attachment is obsolete: true
Attachment #570599 - Flags: review?(robert.bugzilla)
Attachment #570495 - Flags: review?(robert.bugzilla)
- Firefox installer now registers its own allowed certificate to the hashed registry path
- Firefox uninstaller now removes it from the hashed registry path
Attachment #570507 - Attachment is obsolete: true
Attachment #570600 - Flags: review?(robert.bugzilla)
Attachment #570507 - Flags: review?(robert.bugzilla)
Minor rebasing to remove fuzz.
Attachment #570522 - Attachment is obsolete: true
Attachment #570601 - Flags: review?(robert.bugzilla)
Attachment #570522 - Flags: review?(robert.bugzilla)
Overall state of this task:

Other than future review comments, all known work for this bug is done.
I still need to test on a variety of different platform and different UAC settings but it's all working nicely on Win7 with the default UAC level.

Everything works best if you define DISABLE_SERVICE_AUTHENTICODE_CHECK since we don't have a signed updater.exe yet on Nightly.  Also this allows the callback to be called since the Nightly firefox.exe isn't signed (and won't be signed with the temp workaround).  The temp workaround of committing a frozen updater.exe will make updates work even if DISABLE_SERVICE_AUTHENTICODE_CHECK  is not defined.

Things to decide before we land:
We need to decide if we want the callback check to be always disabled until we have nightly auto signed, or if we want to NOT call the callback app after updates because it is not signed.  I.e. after this lands, an updated firefox.exe won't be relaunched currently on Nightly if DISABLE_SERVICE_AUTHENTICODE_CHECK is not defined because it is not signed.  We can't use the temp workaround (of committing a frozen) on firefox.exe because it changes too frequently. We can either 1) NOT do the sign check for the callback app (a security problem) or 2) have it not auto relaunch firefox.exe after updates on Nightly.   This concern is all temporary until Nightly gets auto signed.
(In reply to Brian R. Bondy [:bbondy] from comment #243)
> Things to decide before we land:
> We need to decide if we want the callback check to be always disabled until
> we have nightly auto signed, or if we want to NOT call the callback app
> after updates because it is not signed.  I.e. after this lands, an updated
> firefox.exe won't be relaunched currently on Nightly if
> DISABLE_SERVICE_AUTHENTICODE_CHECK is not defined because it is not signed. 
> We can't use the temp workaround (of committing a frozen) on firefox.exe
> because it changes too frequently. We can either 1) NOT do the sign check
> for the callback app (a security problem) or 2) have it not auto relaunch
> firefox.exe after updates on Nightly.   This concern is all temporary until
> Nightly gets auto signed.

#2 is a pretty bad user experience, but i'm still pretty concerned about running unsigned callback app's in another user's session. even with requiring the signed callback app, you could possibly downgrade another user's Firefox perhaps.
Comment on attachment 568684 [details] [diff] [review]
Patch 7 - RAII base helpers

Review of attachment 568684 [details] [diff] [review]:
-----------------------------------------------------------------

::: xpcom/base/nsAutoHandle.h
@@ +44,5 @@
> +  nsAutoHandle(HANDLE handle = INVALID_HANDLE_VALUE) : mHandle(handle)
> +  {
> +  }
> +
> +  void forget() 

In XPCOM land, forget() implies the caller is taking ownership of the value so the container should simply forget it (without a close or release). Perhaps these should be something like 'close()' so people don't mistake what they do.

::: xpcom/base/nsAutoRegKey.h
@@ +35,5 @@
> + *
> + * ***** END LICENSE BLOCK ***** */
> +
> +#ifndef nsAutoHandle_h___
> +#define nsAutoHandle_h___

nit - nsAutoRegKey_h___
Attachment #568684 - Flags: review?(jmathies) → review+
Attachment #570601 - Flags: review?(robert.bugzilla) → review?(jmathies)
Attachment #570595 - Flags: review?(robert.bugzilla) → review?(jmathies)
Attachment #570596 - Flags: review?(robert.bugzilla) → review?(jmathies)
Comment on attachment 568684 [details] [diff] [review]
Patch 7 - RAII base helpers

Could these be replaced with nsAutoRef specializations? See nsAutoRef.h
Comment on attachment 570596 [details] [diff] [review]
Patch 12 - Simple SC plugin inclusion. v3.

Review of attachment 570596 [details] [diff] [review]:
-----------------------------------------------------------------

::: other-licenses/nsis/Contrib/ServicesHelper/Services.cpp
@@ +62,5 @@
> +  SC_HANDLE serviceManager = ::OpenSCManager(NULL, NULL, 
> +                                             SC_MANAGER_ENUMERATE_SERVICE);
> +  if (!serviceManager) {
> +    exists = FALSE;
> +    return FALSE;

Why don't you init exists = FALSE; at the top, then there's no risk of leaking this without a set, and you can ditch the paren here and the assignments to FALSE below. Which should clean things up a bit.

@@ +126,5 @@
> +static BOOL
> +StopService(LPCWSTR serviceName)
> +{
> +  // Get a handle to the local computer SCM database with full access rights.
> +  SC_HANDLE serviceManager = ::OpenSCManager(NULL, NULL, 

nit - you're using '::' here and in some cases your not. I'd suggest either ditching the use or use it everywhere. Your call.

@@ +148,5 @@
> +  static const int maxWaitTime = 1000 * 60; // Never wait more than a minute
> +  BOOL stopped = FALSE;
> +  if (ControlService(serviceHandle, SERVICE_CONTROL_STOP, &status)) {
> +    do {
> +      Sleep(status.dwWaitHint);

So if we get hung up in here for a while, what happens? Does the install UI freeze, or is this only called from the background? If there's UI dependent on this, maybe we need to be spinning a dispatch loop in here?
- Removed :: throughout since it would be an uphill battle to keep doing it
- Did suggestion for exists = FALSE at the start

> So if we get hung up in here for a while, what happens? 
> Does the install UI freeze, or is this only called from the background? 
> If there's UI dependent on this, maybe we need to be spinning a dispatch loop in here?
  
It never takes longer than instant for me, but I did put a ::Sleep for 15 seconds for testing purposes to see what happens.
The UI does not freeze and you can move the window around fine so it's not a problem like it would be by default on Win32 on the UI thread.
Attachment #570596 - Attachment is obsolete: true
Attachment #570835 - Flags: review?(jmathies)
Attachment #570596 - Flags: review?(jmathies)
Attachment #570835 - Flags: review?(jmathies) → review+
Attachment #570143 - Flags: review?(robert.bugzilla) → review+
(In reply to ben turner [:bent] from comment #246)
> Comment on attachment 568684 [details] [diff] [review] [diff] [details] [review]
> Patch 7 - RAII base helpers
> 
> Could these be replaced with nsAutoRef specializations? See nsAutoRef.h
Brian, after answering this question please request sr from bsmedberg. Thanks
> Could these be replaced with nsAutoRef specializations? See nsAutoRef.h
> Brian, after answering this question please request sr from bsmedberg. Thanks

Yes they can, I just finished replacing them and will re-review jimm and if he r+ I'll sr? bsmedberg.  Thanks for the suggestion Ben T.
Depends on: 698837
Attached patch Patch 7 - RAII base helpers. v2. (obsolete) — Splinter Review
Recoded these so they are specializations of nsAutoRef.
Attachment #568684 - Attachment is obsolete: true
Attachment #571099 - Flags: review?(jmathies)
Same as last patch, just updated syntax at a couple places for nsAutoRef based RAII helpers.
Attachment #570146 - Attachment is obsolete: true
Attachment #571105 - Flags: review?(robert.bugzilla)
Attachment #570146 - Flags: review?(robert.bugzilla)
Same as last patch, just updated syntax at a couple places for nsAutoRef based RAII helpers.
Attachment #570597 - Attachment is obsolete: true
Attachment #571107 - Flags: review?(robert.bugzilla)
Attachment #570597 - Flags: review?(robert.bugzilla)
Same as last patch, just updated syntax at a couple places for nsAutoRef based RAII helpers.
Attachment #570148 - Attachment is obsolete: true
Attachment #571110 - Flags: review?(robert.bugzilla)
Attachment #570148 - Flags: review?(robert.bugzilla)
Comment on attachment 570601 [details] [diff] [review]
Patch 19 - Create the updatedir from the installer and set permissions on it to full access. v1'.

So there's an open question here whether we should break this down per user. bbondy will kick off a security review on this with some detail on what goes in here. Code looks ok though for what we are currently doing.
Attachment #570601 - Flags: review?(jmathies) → review+
(In reply to Brian R. Bondy [:bbondy] from comment #243)
I'd prefer disabling the check for now so it relaunches Firefox. It would be best to run this by the security team first.
Same as last patch, just updated syntax at a couple places for nsAutoRef based RAII helpers.
Attachment #570598 - Attachment is obsolete: true
Attachment #571116 - Flags: review?(robert.bugzilla)
Attachment #570598 - Flags: review?(robert.bugzilla)
Comment on attachment 570601 [details] [diff] [review]
Patch 19 - Create the updatedir from the installer and set permissions on it to full access. v1'.

Ian we'd like a security review on this patch. 

We aren't sure which user will initially create that directory, so to ensure that there are no issues with non-owners being able to write to it we set full access on that directory.

Work items for the service go into that directory.
I'm not sure if there are any security concerns for putting full access on a folder like that.
Attachment #570601 - Flags: review?(imelven)
Fixed bug found when testing upgrades of the maint service itself.
Attachment #570599 - Attachment is obsolete: true
Attachment #571225 - Flags: review?(robert.bugzilla)
Attachment #570599 - Flags: review?(robert.bugzilla)
Comment on attachment 570496 [details] [diff] [review]
Patch 10 - Shared installer code and service upgrade. v2.

I'm going to move the updating of the service itself to the end of the service cpp code itself.  After it calls the callback app it'll launch maintenanceservice installer itself.  Doing it from the /PostUpdate won't work because that happens before the callback is launched.  And we launch the callback from within the service.  

I will re-upload this patch later today with the code from shared.nsh removed and a new small patch 21 which does the self update.
Attachment #570496 - Flags: review?(robert.bugzilla)
Updated code to not do update from /PostUpdate.  See patch 21 for new way service self updates itself.
Attachment #570496 - Attachment is obsolete: true
Attachment #571348 - Flags: review?(robert.bugzilla)
Attachment #571349 - Flags: review?(robert.bugzilla)
Same logic as last patch, just fixed the file's properties. Company name was showing up as  ${CompanyName} before this patch.
Attachment #571225 - Attachment is obsolete: true
Attachment #571427 - Flags: review?(robert.bugzilla)
Attachment #571225 - Flags: review?(robert.bugzilla)
I added writing the update.status file to "pending-no-service" before we launch the service work-item command.  This is basically a catch all to ensure we don't get into a situation were the service always fails (example a crash in the service) when applying updates.  So basically for a single update we will only ever try to use the service at most once. If it fails we automatically go back to the old method.

Updated patch for that coming later today.
Attachment #571099 - Flags: superreview?(benjamin)
Attachment #571099 - Flags: review?(jmathies)
Attachment #571099 - Flags: review+
Comment on attachment 570595 [details] [diff] [review]
Patch 20 - Path hashing for unique registry location

Review of attachment 570595 [details] [diff] [review]:
-----------------------------------------------------------------

I plucked this out and ran it in a stand alone app, didn't see any issues. Discussed with Brian possibly using some of our existing md5 code, but in the end we both agreed we liked the self contained nature of this as is and didn't want to hassle with linking up other libraries.

::: toolkit/components/maintenanceservice/pathhash.cpp
@@ +52,5 @@
> +                      LPWSTR hexString)
> +{
> +  WCHAR *p = hexString;
> +  for (DWORD i = 0; i < hashSize; ++i) {
> +    wsprintfW(p, L"%.2x",hash[i]);

silly nit - space after 2x",

@@ +130,5 @@
> +CalculateRegistryPathFromFilePath(const LPCWSTR filePath, 
> +                                  LPWSTR registryPath)
> +{
> +  size_t filePathLen = wcslen(filePath); 
> +  if (filePathLen == 0)

nit - if (!filePathLen)

@@ +149,5 @@
> +  }
> +  
> +  LPCWSTR baseRegPath = L"SOFTWARE\\Mozilla\\"
> +                        L"MaintenanceService\\";
> +  wcsncpy(registryPath, baseRegPath, MAX_PATH);

To avoid the depreciated and security warnings, I think we can use wcsncpy_s here, which was available in vc 2005 and up.
Attachment #570595 - Flags: review?(jmathies) → review+
(In reply to Brian R. Bondy [:bbondy] from comment #257)
> Created attachment 571116 [details] [diff] [review] [diff] [details] [review]
> Patch 4 - Certificate check code inside service. v3'.
> 
> Same as last patch, just updated syntax at a couple places for nsAutoRef
> based RAII helpers.

Having mucked around with the nightmare that is WinVerifyTrust in a previous life, this usage looked ok to me.
Comment on attachment 570601 [details] [diff] [review]
Patch 19 - Create the updatedir from the installer and set permissions on it to full access. v1'.

The security team discussed this at our team meeting today. Due to the MAR files themselves not being signed, it seems like a malicious local user (or malware, to whom this would be very attractive) could create a work item that's a self-extracting exe containing a signed updater.exe and a bogus MAR file that would install something evil. so we would obviously prefer that the directory not be world writeable. 

Additionally, if the assumption that the callback app from the work item is a path that is executed as the callback, I'm now less worried about the callback app not being signed since it can only be an existing executable that's run in the arbitrary user's session (it's still pretty bad if the work item can pass arguments to the executable though e.g. del /s c:)

we are happy to meet to discuss further if needed :)
Attachment #570601 - Flags: review?(imelven)
another followup question that came up as well : how does the installer know which Firefox to update if there's more than one installed on the system ? is this one of the arguments/parameters in the work item ?
- Moved function WriteStatusPendingNoService to common nsWindowsRestart.cpp code 
- Now set this status before even attempting to use the service in case there is some kind of unhandled failure or bug
Attachment #571505 - Flags: review?(robert.bugzilla)
> another followup question that came up as well : how does the installer know
> which Firefox to update if there's more than one installed on the system ? 
> is this one of the arguments/parameters in the work item ?

Yes it is. Here is the command line format of updater.exe. All parameters are passed to the service via work item file and are relayed to start updater.exe inside the service.

updater.exe update-dir apply [wait-pid [callback-dir callback-path args]]



> The security team discussed this at our team meeting today. 
> Due to the MAR files themselves not being signed, it seems like a 
> malicious local user (or malware, to whom this would be very attractive) 
> could create a work item that's a self-extracting exe containing a 
> signed updater.exe and a bogus MAR file that would install something 
> evil. so we would obviously prefer that the directory not be world writeable. 

Even without this patch setting full access on the directory, any directory under c:\programData is writable by any user, even limited user accounts.  Do you suggest to instead monitor per user app data folders and only allow sessions belonging to those users to be used? or?
@@ +149,5 @@
> +  }
> +  
> +  LPCWSTR baseRegPath = L"SOFTWARE\\Mozilla\\"
> +                        L"MaintenanceService\\";
> +  wcsncpy(registryPath, baseRegPath, MAX_PATH);

> To avoid the depreciated and security warnings, I think we can use wcsncpy_s 
> here, which was available in vc 2005 and up.

This file in particular is compiled in vc6 (pre vc2005) because of the plugin small file size.   It is also compiled shared and compiled in FF.  I'll do other nits but will leave that one because of that reason.
Even if the temp directory were only user writable it's still a vector for a malware install to elevate to system privilege. If we aren't sure we're installing our stuff then this service is dangerous.
Seems we'll have to do the MAR file signing (follow up task), then land them at the same time.
Implemented review nits.
Attachment #570595 - Attachment is obsolete: true
Attachment #571532 - Flags: review+
(In reply to Brian R. Bondy [:bbondy] from comment #273)
> Seems we'll have to do the MAR file signing (follow up task), then land them
> at the same time.

Any thoughts on how to do this? MAR files currently have no support for attaching signatures, and we've never signed mar files before other than generating detached gpg signatures.
> Any thoughts on how to do this? MAR files currently have no support for 
> attaching signatures, and we've never signed mar files before other 
> than generating detached gpg signatures

I'll let security chime in here but from my understanding we would encrypt the mar file with our private key before deployment.  Then it gets downloaded and decrypted with our public key by updater.exe.
Attachment #571110 - Attachment is patch: true
(In reply to Brian R. Bondy [:bbondy] from comment #276)
> > Any thoughts on how to do this? MAR files currently have no support for 
> > attaching signatures, and we've never signed mar files before other 
> > than generating detached gpg signatures
> 
> I'll let security chime in here but from my understanding we would encrypt
> the mar file with our private key before deployment.  Then it gets
> downloaded and decrypted with our public key by updater.exe.

personally i would be fine with using our certs to verify the mar file. i know that's not necessarily doable for nightly at the moment as we've discussed previously.
Now delete the maintenanceservice.exe from the FF directory after installing the maintenance service so that if you uninstall FF it won't keep that file around.
Attachment #571427 - Attachment is obsolete: true
Attachment #571629 - Flags: review?(robert.bugzilla)
Attachment #571427 - Flags: review?(robert.bugzilla)
Minor logging fix.
Attachment #570145 - Attachment is obsolete: true
Attachment #571638 - Flags: review?(robert.bugzilla)
Attachment #570145 - Flags: review?(robert.bugzilla)
Fixed bug where it would only pass the first callback arg when relaunching the callback app.  So for example "-P Nightly -no-remote" would be treated as only "-P" before hence opening callback app, but with the wrong profile.
Attachment #570517 - Attachment is obsolete: true
Attachment #571693 - Flags: review?
Attachment #570517 - Flags: review?(robert.bugzilla)
Attachment #571693 - Flags: review? → review?(robert.bugzilla)
Rebased due to changes in patch 16.  Same as before.
Attachment #571107 - Attachment is obsolete: true
Attachment #571695 - Flags: review?(robert.bugzilla)
Attachment #571107 - Flags: review?(robert.bugzilla)
Comment on attachment 569786 [details] [diff] [review]
(PUSHED) Patch 8 - NSPR wide string logging support on Windows. v2.

Review of attachment 569786 [details] [diff] [review]:
-----------------------------------------------------------------

This looks pretty reasonable.

::: nsprpub/pr/src/io/prprf.c
@@ +86,5 @@
>  	double d;
>  	const char *s;
>  	int *ip;
> +#ifdef WIN32
> +	const WCHAR *ws;

Is there a reason to prefer WCHAR over wchar_t here?
Attachment #569786 - Flags: review?(ted.mielczarek) → review+
WCHAR is windows specific whereas wchar_t is cross platform.  
The code change is only specific to Windows, so it's a toss up which one is better.

WCHAR and wchar_t should always be the same. 
From WinNT.h:

#ifndef _MAC
typedef wchar_t WCHAR;    // wc,   16-bit UNICODE character
#else
// some Macintosh compilers don't define wchar_t in a convenient location, or define it as a char
typedef unsigned short WCHAR;    // wc,   16-bit UNICODE character
#endif

I think WCHAR was around longer than wchar_t was around, but I could be wrong on that (I think really really old VC++ versions only supported WCHAR).

Here are the usages of WCHAR and wchar_t currently in use in the same module (not including my code):
Find all "WCHAR", Subfolders, Find Results 1, "C:\projects\mozilla\mozilla-central\nsprpub\pr", "*.c;*.cpp;*.cxx;*.cc;*.tli;*.tlh;*.h;*.hpp;*.hxx;*.hh;*.inl;*.rc;*.resx;*.idl;*.asm;*.inc;*.idl;"
  C:\projects\mozilla\mozilla-central\nsprpub\pr\include\prtypes.h(480):typedef wchar_t PRUnichar;
  C:\projects\mozilla\mozilla-central\nsprpub\pr\src\io\prlog.c(108):    WCHAR *wMsg = (WCHAR *)PR_Malloc(len * sizeof(WCHAR));
  C:\projects\mozilla\mozilla-central\nsprpub\pr\src\md\windows\w95io.c(49):#include <wchar.h>
  C:\projects\mozilla\mozilla-central\nsprpub\pr\tests\testfile.c(120):WCHAR wPathname[256];
  C:\projects\mozilla\mozilla-central\nsprpub\pr\tests\testfile.c(956):        WCHAR tdir[TMPDIR_LEN];
  C:\projects\mozilla\mozilla-central\nsprpub\pr\tests\thrpool_server.c(97):    wchar_t wpath[MAX_PATH];
  C:\projects\mozilla\mozilla-central\nsprpub\pr\tests\tmocon.c(79):    wchar_t wpath[MAX_PATH];
  Matching lines: 11    Matching files: 7    Total files searched: 431


I provided a second variant of Patch 8 based on wchar_t.  
I'll let you make the final call which one to use, they both work for me.
Attachment #569786 - Attachment is obsolete: true
Attachment #571723 - Flags: review+
Attachment #569786 - Attachment is obsolete: false
Okay, that seems fine. I landed the original patch:
Checking in pr/src/io/prprf.c;
/cvsroot/mozilla/nsprpub/pr/src/io/prprf.c,v  <--  prprf.c
new revision: 3.21; previous revision: 3.20
done
And I pushed an NSPR update (including this patch) to inbound:
https://hg.mozilla.org/integration/mozilla-inbound/rev/765f5b79fedf
Comment on attachment 571723 [details] [diff] [review]
Patch 8 - NSPR wide string logging v 2.2 (same as v2 but based on wchar_t instead of WCHAR)

bbondy: thank you for the patch.  In general NSPR features
are implemented for all platforms.  Why is the %S format
specifier only implemented for Windows?  Can we use the
wcrtomb or wcsrtomb function on Unix?

Is %ls a more standard format than %S for wchar_t strings?
wtc: I tested on g++4.5 and also vc++ and they both accept the %S formatting specifier and also %ls formatting specifier.

I'll post a linked ticket shortly to do a follow up patch to patch 8 which will add cross platform support and also accept %ls in addition to %S.  I'll CC you on that ticket.
Blocks: 699567
For the record, MinGW-w32/w64 4.7.0 reject %S when compiling with -pedantic. %ls appears to be ISO-C++.
Just an FYI in case anyone is interested in the history of %S vs. %ls format specifiers.

The C++ 2003 standard doesn't make any mention of it that I can find.
The C99 standard does though:

"The original intent was to add the new conversion specifiers %S and %C to the existing formatted input and output functions to handle a wide character string and a wide character respectively.  After long discussions about the actual implementation and future library directions, these specifiers were withdrawn. They were replaced with the qualified conversion specifiers, %ls and %lc, with the addition of %l[...] in the formatted input functions"

Likely some compilers implemented %S before it was standardized and never removed support.
Depends on: 699700
(In reply to Ted Mielczarek [:ted, :luser] from comment #284)
> Okay, that seems fine. I landed the original patch:
> Checking in pr/src/io/prprf.c;
> /cvsroot/mozilla/nsprpub/pr/src/io/prprf.c,v  <--  prprf.c
> new revision: 3.21; previous revision: 3.20
> done

Merged to m-c:
https://hg.mozilla.org/mozilla-central/rev/765f5b79fedf
No longer blocks: 692638
No longer blocks: 692887
Blocks: bgupdates
No longer do rename maintenanceservice.exe -> maintenanceservice_tmp.exe, copy, then delete source _tmp file inside the FF directory because it will cause problems when doing incremental partial updates.
Attachment #571629 - Attachment is obsolete: true
Attachment #572040 - Flags: review?(robert.bugzilla)
Attachment #571629 - Flags: review?(robert.bugzilla)
bbondy: please do not start using the %S formatting specifier.  Please wait
until bug 699567 is fixed, and then use the %ls formatting specifier instead.

I will unsubscribe from this bug but remain on bug 699567 for the NSPR work.
Thanks!
When updating with limited user accounts it would attempt to show a GUI.  On Vista+ this would show a "service is trying to show a GUI click here to see it dialog" popup.

This is fixed now, we no longer try to show a GUI if trying to start the update in session 0 on Vista or later.
Attachment #571693 - Attachment is obsolete: true
Attachment #572150 - Flags: review?(robert.bugzilla)
Attachment #571693 - Flags: review?(robert.bugzilla)
Re wtc:
> bbondy: please do not start using the %S formatting specifier.  Please wait
> until bug 699567 is fixed, and then use the %ls formatting specifier instead.

Those references will be changed at the same time as bug 699567 is fixed.
Comment on attachment 571099 [details] [diff] [review]
Patch 7 - RAII base helpers. v2.

Please remove the trailing underscores from the #defines e.g. just "nsAutoRegKey_h" not "nsAutoRegKey_h__".

Is there a particular reason to have these all in separate files instead of just nsWindowsHelpers.h or something like that? r=me either way.
Attachment #571099 - Flags: superreview?(benjamin) → superreview+
Extracted strings from other patches.
Attachment #572590 - Flags: review?(robert.bugzilla)
Attachment #572590 - Attachment description: Strings for localization → Patch 23 - Strings for localization
Comment on attachment 572590 [details] [diff] [review]
(PUSHED) Patch 23 - Strings for localization

Looks good... please get this landed before the branch merge.
Attachment #572590 - Flags: review?(robert.bugzilla) → review+
Pushed Patch 23 to mozilla-inbound:
Brian R. Bondy - Bug 481815 - Strings for localization relating to maintenance service. r=rs
http://hg.mozilla.org/integration/mozilla-inbound/rev/375aaf23cc2f
Attachment #571723 - Attachment is obsolete: true
Attachment #571723 - Flags: review+
Attachment #569786 - Attachment description: Patch 8 - NSPR wide string logging support on Windows. v2. → (PUSHED) Patch 8 - NSPR wide string logging support on Windows. v2.
Attachment #572590 - Attachment description: Patch 23 - Strings for localization → (PUSHED) Patch 23 - Strings for localization
Attached patch Patch 7 - RAII base helpers. v3. (obsolete) — Splinter Review
- Fixed header guard style
- Merged the 3 RAII helpers into nsWindowsHelpers.h
- Extracted the build config Makefile.in from patch 14 into this patch (Patch 7).
Attachment #571099 - Attachment is obsolete: true
Attachment #572647 - Flags: superreview?(benjamin)
Attachment #572647 - Flags: review+
Rebased. Used the renamed nsWindowsHelpers.h instead of old include files after review.
Attachment #571638 - Attachment is obsolete: true
Attachment #572652 - Flags: review?(robert.bugzilla)
Attachment #571638 - Flags: review?(robert.bugzilla)
Rebased. Used the renamed nsWindowsHelpers.h instead of old include files after review.
Attachment #571105 - Attachment is obsolete: true
Attachment #572654 - Flags: review?(robert.bugzilla)
Attachment #571105 - Flags: review?(robert.bugzilla)
Rebased. Used the renamed nsWindowsHelpers.h instead of old include files after review.
Attachment #571116 - Attachment is obsolete: true
Attachment #572655 - Flags: review?(robert.bugzilla)
Attachment #571116 - Flags: review?(robert.bugzilla)
Rebased. Used the renamed nsWindowsHelpers.h instead of old include files after review.
Attachment #571110 - Attachment is obsolete: true
Attachment #572657 - Flags: review?(robert.bugzilla)
Attachment #571110 - Flags: review?(robert.bugzilla)
Attached patch Patch 14 - Build process. v3'. (obsolete) — Splinter Review
Rebased.  Moved the RAII helper out of this patch and into patch 7.
Attachment #570493 - Attachment is obsolete: true
Attachment #572661 - Flags: review?(robert.bugzilla)
Attachment #570493 - Flags: review?(robert.bugzilla)
Comment on attachment 572590 [details] [diff] [review]
(PUSHED) Patch 23 - Strings for localization

https://hg.mozilla.org/mozilla-central/rev/375aaf23cc2f
Attachment #572590 - Flags: checkin+
(In reply to Robert Strong [:rstrong] (do not email) from comment #297)
> Comment on attachment 572590 [details] [diff] [review] [diff] [details] [review]
> (PUSHED) Patch 23 - Strings for localization
> 
> Looks good... please get this landed before the branch merge.

Prelanding strings in general isn't a good thing to do, and in this particular case, it only helps so far.

Switching on the display of these prefs changes the dimensions in the (l10n) sized pref window, and as such, effectively breaks string freeze, even if it only shows existing strings.
> Prelanding...

Understood.  Just wanted clarify though that there is a good chance that this will be pushed to v10 so it is more landing on time, and postlanding the other patches.
Rebased to mozilla-central tip
Attachment #570600 - Attachment is obsolete: true
Attachment #572799 - Flags: review?(robert.bugzilla)
Attachment #570600 - Flags: review?(robert.bugzilla)
Attached patch Patch 14 - Build process. v3''. (obsolete) — Splinter Review
Rebased to mozilla-central tip
Attachment #572661 - Attachment is obsolete: true
Attachment #572800 - Flags: review?(robert.bugzilla)
Attachment #572661 - Flags: review?(robert.bugzilla)
(In reply to Axel Hecht [:Pike] from comment #306)
> (In reply to Robert Strong [:rstrong] (do not email) from comment #297)
> > Comment on attachment 572590 [details] [diff] [review] [diff] [details] [review] [diff] [details] [review]
> > (PUSHED) Patch 23 - Strings for localization
> > 
> > Looks good... please get this landed before the branch merge.
> 
> Prelanding strings in general isn't a good thing to do, and in this
> particular case, it only helps so far.
> 
> Switching on the display of these prefs changes the dimensions in the (l10n)
> sized pref window, and as such, effectively breaks string freeze, even if it
> only shows existing strings.
That ui is constrained by width and has a min-height
http://mxr.mozilla.org/mozilla-central/source/browser/locales/en-US/chrome/browser/preferences/preferences.dtd#6

The string itself will wrap and if the ui height is not enough to display the string it should increase in height due to some work I did on this ui several years ago.

Whatever the case, we'll make sure the ui isn't broken before turning this on.
Depends on: 701069
Blocks: 701103
Comment on attachment 572652 [details] [diff] [review]
Patch 2 - Main service logic. v4''.

>diff --git a/toolkit/components/maintenanceservice/workmonitor.cpp b/toolkit/components/maintenanceservice/workmonitor.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
>+static const int FILE_SHARE_ALL = FILE_SHARE_READ | 
>+                                  FILE_SHARE_WRITE | 
>+                                  FILE_SHARE_DELETE;
>+
>+// Wait 15 minutes for an update operation to run at most.
>+static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000;
The end result I think we want here is to have the update applied to a copy of the app similar to what Ehsan is doing. Is it feasible to add that at this time and if not when do you see that being added? Is this why you wanted to use the user's account when possible so the ui could be shown?

>+
>+/**
>+ * Runs an update process in the specified sessionID as an elevated process.
>+ *  
>+ * @sessionID     The session ID to run the process as.
http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html
The format should be
@param text  the text of the tool tip

here and elsewhere

check that alignment is correct elsewhere.

I don't think this is an alternative way of formatting but if it is please point me to it.

>+ * @appToStart    The path to the update process to start
>+ * @workingDir    The working directory to execute the update process in
>+ * @cmdLine       The command line parameters to pass to the update process.
>+ * @return        TRUE if the update process was run had a return code of 0.
>+ */
>+BOOL
>+StartUpdateProcess(DWORD sessionID, 
>+                   LPCWSTR appToStart, 
>+                   LPCWSTR workingDir, 
>+                   LPWSTR cmdLine)
>+{
>+  BOOL processStarted = FALSE;
>+  DWORD myProcessID = GetCurrentProcessId();
>+  DWORD mySessionID = 0;
>+  ProcessIdToSessionId(myProcessID, &mySessionID);
>+
>+  STARTUPINFO si = {0};
>+  si.cb = sizeof(STARTUPINFO);
>+  si.lpDesktop = L"winsta0\\Default";
>+  PROCESS_INFORMATION pi = {0};
>+  nsAutoHandle elevatedToken, unelevatedToken;
>+
>+  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+    ("Starting process in an elevated session.  Service "
>+     "session ID: %d; Requested session ID: %d", 
>+     mySessionID, sessionID));
>+
>+  // We must be running this from an older OS than Vista or else
>+  // we are running this code as a user process instead of via the service.
>+  // In that case just execute the process in the normal way.
>+  if (mySessionID == sessionID) {
>+    processStarted = ::CreateProcessW(appToStart, cmdLine, 
>+                                      NULL, NULL, FALSE, 
>+                                      CREATE_DEFAULT_ERROR_MODE | 
>+                                      CREATE_UNICODE_ENVIRONMENT, 
>+                                      NULL, workingDir, &si, &pi);
>+    if (!processStarted) {
>+      DWORD lastError = ::GetLastError();
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not create process as current user, last error: "
>+         "%d; appToStart: %S; cmdLine: %S", 
>+         lastError, appToStart, cmdLine));
>+    }
>+  } else {
>+    unelevatedToken = UACHelper::OpenUserToken(sessionID);
>+    if (!unelevatedToken) {
>+      // Couldn't obtain user token
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not obtain user token, will start as the SYSTEM account "
>+         "instead."));
>+      return StartUpdateProcess(mySessionID, appToStart, workingDir, 
>+                                cmdLine, sessionID);
>+    }
>+
>+    UACHelper::UserType userType;
>+    if (!UACHelper::GetElevationType(unelevatedToken, userType) ||
>+        userType == UACHelper::LimitedUser) {
>+      // Couldn't determine the user type, so just start the app as 
>+      // the SYSTEM account
// Couldn't determine the user type or limited user

>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not determine elevation type, will start as the SYSTEM "
>+         "account instead."));
>+      return StartUpdateProcess(mySessionID, appToStart, workingDir, 
>+                                cmdLine, sessionID);
>+    }
>+
>+    if (userType == UACHelper::AdministratorUnelevated) {
>+      UACHelper::UserType linkedTokenType;
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Windows Vista or later unelevated detected, attempting to find "
>+         "linked token."));
>+      elevatedToken = UACHelper::OpenLinkedToken(unelevatedToken);
>+      BOOL obtainedElevationType = 
>+        UACHelper::GetElevationType(elevatedToken, linkedTokenType);
>+      if (obtainedElevationType && elevatedToken && 
>+          UACHelper::AdministratorElevated == linkedTokenType) {
>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+          ("Elevated token was found, using it."));
Why not just use the system account? I think it would be cleaner with less likelihood of there being weird bugs due to using the user's account in some cases and the system account in others. Also, the system account should always have the rights to apply the update whereas a user won't though I do get that these checks try to prevent that from happening.

The rest of the code in StartUpdateProcess looks good but I'm not diving into it deeply since it will likely change a lot if the above route is taken.

>+        // Proceed on...
>+      } else if (obtainedElevationType && elevatedToken &&
>+                 linkedTokenType != UACHelper::AdministratorElevated) {
>+
nit: remove extra line for consistency

>+        // We got a linked token but strangely enough it's not an elevated one?
>+        // Maybe Windows added a new token type or something or they are 
>+        // linking 2 non elevated tokens.
What cases do you think this will happen? For example, couldn't this happen with the Backup Operators group?

>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+          ("Obtained linked token but it is not elevated, will start as the "
>+           "SYSTEM account instead"));
>+        return StartUpdateProcess(mySessionID, appToStart, workingDir, 
>+                                  cmdLine, sessionID);
>+      } else if (!obtainedElevationType && elevatedToken) {
>+
nit: remove extra line for consistency

>+        // We could not get the elevation type but we did get a linked token.
>+        // This is almost for sure an administrative token so just write out a 
>+        // warning and continue going on like normal.
>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+          ("Warning, could not verify elevation type, but did get a linked "
>+           "token so proceeding."));
>+        // Proceed on...
>+      } else {
>+
nit: remove extra line for consistency

>+        // Couldn't get an elevated token so just start the app as the
>+        // SYSTEM account
s/start the app/start the updater/

At first I thought this was the callback application.

>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+          ("An elevated token was not found, will start as the SYSTEM account "
>+           "instead."));
>+        return StartUpdateProcess(mySessionID, appToStart, workingDir, 
>+                                  cmdLine, sessionID);
>+      }
>+    } 
>+    // else { 
>+    // they are AdministratorElevated or AdministratorUACIsOff, so just use 
>+    // the token we already have 
>+    // }
What's going on here?

>+
>+    // Create an environment block for the process we're about to start using
>+    // the user's token.
>+    if (!::CreateEnvironmentBlock(&environmentBlock, elevatedToken, TRUE)) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not create an environment block, setting it to NULL."));
>+      environmentBlock = NULL;
>+    }
>+
>+    // Make sure that our elevated token is a primary token. 
>+    // It should be, but just do a sanity check to make sure.
>+    // CreateProcessAsUser can only be used with tokens that are primary.
>+    DWORD len;
>+    TOKEN_TYPE tokenType;
>+    if (!::GetTokenInformation(elevatedToken, TokenType, &tokenType, 
>+                               sizeof(tokenType), &len) ||
>+        tokenType != TokenPrimary) {
>+      // This token should be a primary token
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Warning: we should have obtained a primary token, we won't be "
>+         "able to CreateProcessAsUser with the current token, go with the "
>+         "SYSTEM account."));
>+      return StartUpdateProcess(mySessionID, appToStart, workingDir, 
>+                                cmdLine, sessionID);
>+    }
>+ 
>+    // Launch the process in the specified working directory application and 
>+    // command line
>+    processStarted = ::CreateProcessAsUserW(elevatedToken, appToStart, 
>+                                            cmdLine, 
>+                                            NULL, NULL, FALSE,
>+                                            CREATE_DEFAULT_ERROR_MODE | 
>+                                            CREATE_UNICODE_ENVIRONMENT, 
>+                                            environmentBlock, workingDir, 
>+                                            &si, &pi);
>+    if (!processStarted) {
>+      DWORD lastError = ::GetLastError();
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not create update process with user token, last error: "
>+         "%d; appToStart: %S; cmdLine: %S", 
>+         lastError, appToStart, cmdLine));
>+    }
>+  }
>+

I think the following could be cleaned up a bit as follows

>+  BOOL updateWasSuccessful = FALSE;
remove

>+  if (processStarted) {
if (!processStarted)
  return FALSE;

>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Process was started... waiting on result.")); 
>+
>+    // Wait for the updater process to finish
>+    ::WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER);
>+
>+    // Check the return code of updater.exe to make sure we get 0
>+    DWORD returnCode;
>+    if (!::GetExitCodeProcess(pi.hProcess, &returnCode)) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Process finished but could not obtain return code.")); 
>+
>+    // Wait for the updater process to finish
>+    ::WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER);
>+
>+    // Check the return code of updater.exe to make sure we get 0
>+    DWORD returnCode;
>+    if (!::GetExitCodeProcess(pi.hProcess, &returnCode)) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Process finished but could not obtain return code.")); 
>+    } else {
>+
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Process finished with return code %d.", returnCode)); 
>+      // updater returns 0 if successful.
>+      updateWasSuccessful = (returnCode == 0);
>+    }
>+  }
>+
>+  return updateWasSuccessful;
  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
         ("Process was started... waiting on result.")); 

  // Wait for the updater process to finish
  ::WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER);

  // Check the return code of updater.exe to make sure we get 0
  DWORD returnCode;
  if (!::GetExitCodeProcess(pi.hProcess, &returnCode)) {
    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
           ("Process finished but could not obtain return code.")); 
    return FALSE;
  }

  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
         ("Process finished with return code %d.", returnCode)); 
  // updater returns 0 if successful.
  return updateWasSuccessful = (returnCode == 0);
>+}
>+
>+/**
>+ * Processes a work item (file by the name of .mz) and executes
>+ * the command within.
>+ *  
>+ * @monitoringBasePath  The base path that is being monitored.
>+ * @notifyInfo          the notifyInfo struct for the changes.
>+ * @return        Returns TRUE if we want the service to stop.
>+ */
>+BOOL
>+ProcessWorkItem(LPCWSTR monitoringBasePath, 
>+                FILE_NOTIFY_INFORMATION &notifyInfo)
>+{
>+  int filenameLength = notifyInfo.FileNameLength / 
>+                       sizeof(notifyInfo.FileName[0]); 
>+  notifyInfo.FileName[filenameLength] = L'\0';
>+
>+  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+    ("Processing new command meta file: %S", notifyInfo.FileName));
>+
>+  // When the file is ready for processing it will be renamed 
>+  // to have a .mz extension
>+  BOOL haveWorkItem = notifyInfo.Action == FILE_ACTION_RENAMED_NEW_NAME && 
>+                      notifyInfo.FileNameLength > 3 && 
>+                      notifyInfo.FileName[filenameLength - 3] == L'.' &&
>+                      notifyInfo.FileName[filenameLength - 2] == L'm' &&
>+                      notifyInfo.FileName[filenameLength - 1] == L'z';
>+  if (!haveWorkItem) {
>+    // We don't have a work item, keep looking
>+    return FALSE;
>+  }
>+
>+  WCHAR fullMetaUpdateFilePath[MAX_PATH + 1];
>+  wcscpy(fullMetaUpdateFilePath, monitoringBasePath);
>+
>+  // We only support file paths in monitoring directories that are MAX_PATH
>+  // chars or less.
>+  if (!PathAppendSafe(fullMetaUpdateFilePath, notifyInfo.FileName)) {
>+    // Cannot process update, skipfileSize it.
>+    return TRUE;
>+  }
>+
>+  nsAutoHandle metaUpdateFile = ::CreateFile(fullMetaUpdateFilePath, 
>+                                             GENERIC_READ, 
>+                                             FILE_SHARE_ALL, NULL, 
>+                                             OPEN_EXISTING,
>+                                             0, NULL);
Why is that opened with FILE_SHARE_ALL? Is it in case the service is reading it while it is being written to? Please add a comment as to why.

>+  if (INVALID_HANDLE_VALUE == metaUpdateFile) {
>+
nit: remove extra line for consistency

>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not open command meta file: %S", notifyInfo.FileName));
>+    return TRUE;
>+  }
>+
>+  DWORD fileSize = GetFileSize(metaUpdateFile, NULL);
>+  DWORD sessionID = 0;
>+  // The file should be in wide characters so if it's of odd size it's
>+  // an invalid file.
>+  const int kSanityCheckFileSize = 1024 * 64;
>+  if (fileSize == INVALID_FILE_SIZE || 
>+      (fileSize %2) != 0 ||
>+      fileSize > kSanityCheckFileSize ||
>+      fileSize < sizeof(DWORD)) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not obtain file size or an improper file size was encountered "
>+       "for command meta file: %S", 
>+       notifyInfo.FileName));
>+    return TRUE;
>+  }
>+
>+  // The first 4 bytes are for the process ID
>+  DWORD sessionIDCount;
>+  BOOL result = ::ReadFile(metaUpdateFile, &sessionID, 
>+                            sizeof(DWORD), &sessionIDCount, NULL);
>+  fileSize -= sizeof(DWORD);
>+
>+  // The next MAX_PATH wchar's are for the app to start
>+  WCHAR appToStart[MAX_PATH + 1];
>+  ZeroMemory(appToStart, sizeof(appToStart));
>+  DWORD appToStartCount;
>+  result |= ::ReadFile(metaUpdateFile, appToStart, 
>+                       MAX_PATH * sizeof(WCHAR), &appToStartCount, NULL);
>+  fileSize -= appToStartCount;
>+
>+  // The next MAX_PATH wchar's are for the app to start
>+  WCHAR workingDirectory[MAX_PATH + 1];
>+  ZeroMemory(workingDirectory, sizeof(workingDirectory));
>+  DWORD workingDirectoryCount;
>+  result |= ::ReadFile(metaUpdateFile, workingDirectory, 
>+                       MAX_PATH * sizeof(WCHAR), &workingDirectoryCount, NULL);
>+  fileSize -= workingDirectoryCount;
>+
>+  // + 2 for wide char termination
>+  nsAutoArrayPtr<char> cmdlineBuffer = new char[fileSize + 2];
>+  DWORD cmdLineBufferRead;
>+  result |= ::ReadFile(metaUpdateFile, cmdlineBuffer, 
>+                       fileSize, &cmdLineBufferRead, NULL);
>+  fileSize -= cmdLineBufferRead;
>+
>+  if (!result ||
>+      sessionIDCount != sizeof(DWORD) || 
>+      appToStartCount != MAX_PATH * sizeof(WCHAR) ||
>+      workingDirectoryCount != MAX_PATH * sizeof(WCHAR) ||
>+      fileSize != 0) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not read command data for command meta file: %S", 
>+       notifyInfo.FileName));
>+    return TRUE;
>+  }
>+  cmdlineBuffer[cmdLineBufferRead] = L'\0';
>+  cmdlineBuffer[cmdLineBufferRead + 1] = '\0';
>+  WCHAR *cmdlineBufferWide = reinterpret_cast<WCHAR*>(cmdlineBuffer.get());
>+
>+  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+    ("An update command was detected and is being processed for command meta "
>+     "file: %S", 
>+     notifyInfo.FileName));
>+
>+  // Validate the certificate of the app the user wants us to start.
>+  // Also check to make sure the certificate is trusted.
>+#ifndef DISABLE_SERVICE_AUTHENTICODE_CHECK
>+  if (DoesBinaryMatchAllowedCertificates(appToStart)) {
>+#endif
>+    if (!StartUpdateProcess(sessionID, appToStart, workingDirectory, 
>+                            cmdlineBufferWide)) {
>+      // TODO: Need to tell the app that we couldn't run the update so 
>+      // the app will do the update the old way. rs do you think via 
>+      // update status file? I don't want it to keep using the service 
>+      // in case it keeps getting the same error on each launch.
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Error running process in session %d.  Last error: %d", 
>+         sessionID, GetLastError()));
>+    } else {
>+      // The update was executed and run successfully
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("updater.exe was launched and run successfully"));
>+    }
>+#ifndef DISABLE_SERVICE_AUTHENTICODE_CHECK
>+  } else {
>+    // TODO: Need to tell the app that we couldn't run the update
>+    // because the cert is not valid.  The app will do the update 
>+    // itself the old way.  rs do you think via update status file?
Yes

>+    // I don't want it to keep using the service in case it keeps getting 
>+    // the same error on each launch.
Might be a good thing to just have a counter in a pref that gets reset after success and incremented on failure. When the failure count reach X number of failures disable using the service using the pref you added for this purpose.

>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not start process due to certificate check.  Last error: %d", 
>+       GetLastError()));
>+  }
>+#endif
>+
>+  // We processed a work item, whether or not it was successful.
>+  return TRUE;
>+}
>+
>+/**
>+ * Starts monitoring the update directory for work items.
>+ */
>+BOOL
>+StartDirectoryChangeMonitor() 
>+{
>+  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+    ("Starting directory change monitor..."));
>+
>+  // Init the update directory path and ensure it exists.
>+  // Example: C:\programData\Mozilla\Firefox\updates\[channel]
>+  // The channel is not included here as we want to monitor the base directory
>+  WCHAR updateData[MAX_PATH + 1];
>+  if (!GetUpdateDirectoryPath(updateData)) {
>+
nit: remove extra line for consistency

>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not obtain update directory path"));
>+
>+    return FALSE;
>+  }
>+
>+  // Obtain a directory handle, FILE_FLAG_BACKUP_SEMANTICS is needed for this.
>+  nsAutoHandle directoryHandle = ::CreateFile(updateData, 
>+                                              FILE_LIST_DIRECTORY, 
>+                                              FILE_SHARE_ALL, 
>+                                              NULL, 
>+                                              OPEN_EXISTING,
>+                                              FILE_FLAG_BACKUP_SEMANTICS, 
>+                                              NULL);
>+  if (INVALID_HANDLE_VALUE == directoryHandle) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not obtain directory handle to monitor"));
>+
>+    return FALSE;
>+  }
>+
>+  // Allocate a buffer that is big enough to hold 128 changes
>+  // Note that FILE_NOTIFY_INFORMATION is a variable length struct
>+  // so that's why we don't create a simple array directly.
>+  char fileNotifyInfoBuffer[(sizeof(FILE_NOTIFY_INFORMATION) + 
>+                            MAX_PATH) * 128];
>+  ZeroMemory(&fileNotifyInfoBuffer, sizeof(fileNotifyInfoBuffer));
>+  
>+  DWORD bytesReturned;
>+  // Listen for directory changes to the update directory
>+  while (ReadDirectoryChangesW(directoryHandle, 
>+                               fileNotifyInfoBuffer, 
>+                               sizeof(fileNotifyInfoBuffer), 
>+                               TRUE, FILE_NOTIFY_CHANGE_FILE_NAME, 
>+                               &bytesReturned, NULL, NULL)) {
>+    char *fileNotifyInfoBufferLocation = fileNotifyInfoBuffer;
>+
>+    // Make sure we have at least one entry to process
>+    while (bytesReturned/sizeof(FILE_NOTIFY_INFORMATION) > 0) {
>+
nit: remove extra line for consistency

>+      // Point to the current directory info which is changed
>+      FILE_NOTIFY_INFORMATION &notifyInfo = 
>+        *((FILE_NOTIFY_INFORMATION*)fileNotifyInfoBufferLocation);
>+      fileNotifyInfoBufferLocation += notifyInfo .NextEntryOffset;
>+      bytesReturned -= notifyInfo .NextEntryOffset;
>+      if (!wcscmp(notifyInfo.FileName, L"stop") && gServiceStopping) {
>+
nit: remove extra line for consistency

>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+          ("A stop command was issued."));
>+
>+        return TRUE;
>+      }
>+
Attachment #572652 - Flags: review?(robert.bugzilla) → review-
(In reply to Robert Strong [:rstrong] (do not email) from comment #311)
>...
> >+  return updateWasSuccessful;
>   PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>          ("Process was started... waiting on result.")); 
> 
>   // Wait for the updater process to finish
>   ::WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER);
> 
>   // Check the return code of updater.exe to make sure we get 0
>   DWORD returnCode;
>   if (!::GetExitCodeProcess(pi.hProcess, &returnCode)) {
>     PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>            ("Process finished but could not obtain return code.")); 
>     return FALSE;
>   }
> 
>   PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>          ("Process finished with return code %d.", returnCode)); 
>   // updater returns 0 if successful.
>   return updateWasSuccessful = (returnCode == 0);
> >+}
s/return updateWasSuccessful = (returnCode == 0);/return (returnCode == 0);/
Comment on attachment 572654 [details] [diff] [review]
Patch 3 - Code for installing the service. v3''.

>diff --git a/toolkit/components/maintenanceservice/serviceinstall.cpp b/toolkit/components/maintenanceservice/serviceinstall.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/serviceinstall.cpp
>@@ -0,0 +1,441 @@
>...
>+#include "serviceinstall.h"
>+#include "servicebase.h"
>+#include "shellapi.h"
>+
>+#pragma comment(lib, "version.lib")
>+#define DEFERRED_DELETE_TIMEOUT_SECONDS 10
When ever there is a number without a reason for the value like above I am going to ask why you chose this number unless there is a comment stating why the number was chosen ;)

>+
>+/**
>+ * Obtains the version number from the specified PE file's version information
>+ * Version Format: major.minor.aux.maintenance (Example 10.0.0.300)
>+ *  
>+ * @path        The path of the file to check the version on
>+ * @major       The major part of the version number
>+ * @minor       The minor part of the version number
>+ * @maintenance The maintenance part of the version number
>+ * @return      TRUE if successful
Please add aux

Also note previous review comment regarding formatting

Our naming for each field in a version doesn't fall very well into everyone else's. Instead of using major, minor, etc. since these are really just determined by the app's naming scheme how about using the Windows stuct names for each?
fileVerMS
fileVerLS
prodVerMS
prodVerLS

or something similar to avoid confusion. For example, Firefox uses major, ext_compat_change_for_major_ver, minor, typically_not_used_but_can_be ;)

>+ */
>+static BOOL
>+GetVersionNumberFromPath(LPWSTR path, DWORD &major, DWORD &minor, 
>+                         DWORD &aux, DWORD &maintenance) 
>+{
>+  DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0);
>+  nsAutoArrayPtr<char> fileVersionInfo = new char[fileVersionInfoSize];
>+  if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize,
>+                           fileVersionInfo.get())) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not obtain file info of old service.  (%d)", GetLastError()));
>+      return FALSE;
If we can't get the file info for the old service then I would be concerned that someone replaced our service. Do you think it might be better to just replace it for that case?
> If we can't get the file info for the old service then I would be
> concerned that someone replaced our service.

That sounds fair, I'll make that change.  I'm not sure in which cases that function could fail but likely only in the case you stated.
Attached patch Patch 3.5 temp - Review comments (obsolete) — Splinter Review
This patch will be merged into patch 3 once they reach r+.  I'm keeping the reviews on patch 3 separate for now to make reviewing less painful.
Attachment #573420 - Flags: review?(robert.bugzilla)
Blocks: 701372
> The end result I think we want here is to have the update applied to a copy 
> of the app similar to what Ehsan is doing. Is it feasible to add that at 
> this time and if not when do you see that being added? Is this why you 
> wanted to use the user's account when possible so the ui could be shown?

Right, this task is under the assumption that it will land on Nightly before Ehsan's work.  Once the patches in this ticket get all r+ I will push to Nightly and also push all of the work to the oak branch.  Once it is on the Oak branch Ehsan will also merge his work to the point where it compiles but maybe doesn't have working updates.  From there any additional integration work needed for the service will be done by me.

This process is outlined in these bugs:
Bug 701372 - Push silent update w/ service changes to oak branch (me)
Bug 701375 - Merge background updates to the oak branch (ehsan)
Bug 701379 - Fix silent update service to work with background updates (me)
Comment on attachment 572654 [details] [diff] [review]
Patch 3 - Code for installing the service. v3''.

>diff --git a/toolkit/components/maintenanceservice/serviceinstall.cpp b/toolkit/components/maintenanceservice/serviceinstall.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/serviceinstall.cpp
>@@ -0,0 +1,441 @@
>...
>+ */
>+static BOOL
>+GetVersionNumberFromPath(LPWSTR path, DWORD &major, DWORD &minor, 
>+                         DWORD &aux, DWORD &maintenance) 
>+{
>+  DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0);
>+  nsAutoArrayPtr<char> fileVersionInfo = new char[fileVersionInfoSize];
>+  if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize,
>+                           fileVersionInfo.get())) {
consistency nit
::GetFileVersionInfoW

here and elsewhere

Also, indentation looks off here and immediately below

>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not obtain file info of old service.  (%d)", GetLastError()));
>+      return FALSE;
>+  }
>+
>+  VS_FIXEDFILEINFO *fixedFileInfo = 
>+    reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get());
>+  UINT size;
>+  if (!VerQueryValueW(fileVersionInfo.get(), L"\\", 
>+    reinterpret_cast<LPVOID*>(&fixedFileInfo), &size)) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not query file version info of old service.  (%d)", 
>+         GetLastError()));
>+      return FALSE;
>+  }  
>+
>+  major = HIWORD(fixedFileInfo->dwFileVersionMS);
>+  minor = LOWORD(fixedFileInfo->dwFileVersionMS);
>+  aux = HIWORD(fixedFileInfo->dwFileVersionLS);
>+  maintenance = LOWORD(fixedFileInfo->dwFileVersionLS);
>+  return TRUE;
>+}
>+
>+/**
>+ * Deletes a file after DEFERRED_DELETE_TIMEOUT_SECONDS seconds
>+ *  
>+ * @pathToDelete  The path to delete
>+ * @return        ERROR_SUCCESS if the operation was scheduled
>+ */
>+DWORD
>+DeferredDeletePath(LPCWSTR pathToDelete) 
>+{
>+  WCHAR deferredSlefDeleteCmdLine[MAX_PATH + 32];
>+  wsprintfW(deferredSlefDeleteCmdLine, 
>+            L"/C SLEEP %i && DEL \"%s\"", 
>+            DEFERRED_DELETE_TIMEOUT_SECONDS, 
>+            pathToDelete);
>+
>+  SetLastError(ERROR_SUCCESS);
>+  ShellExecuteW(NULL, L"open", L"cmd", deferredSlefDeleteCmdLine, 
>+                NULL, SW_HIDE);
ewww :(

NSIS has code that handles this for the uninstaller that I suspect is a better solution.

Also, I would suspect that you could do a rename of the file in use to get it out of the way.

>+  return GetLastError();
>+}
>+
>+/**
>+ * Installs or upgrades the service for the process we are running as.
>+ * If an existing service is already installed, we replace it with ourselves
>+ * if we are newer than what is already installed.
>+ *  
>+ * @upgradeOnly   If TRUE works the same but will not install
>+ * @return        TRUE if the service was installed/upgraded
>+ */
>+BOOL
>+SvcInstall(BOOL upgradeOnly)
>+{
For the upgrade case will this happen prior to relaunching the callback application? If so, I don't think we want that. ;)
> For the upgrade case will this happen prior to relaunching the 
> callback application? If so, I don't think we want that. ;)

Nope, this is already worked out.  The service will:
i) launch updater w/o callback parameters in session 0. 
ii) launch the callback in the user session w/ medium integrity (unelevated).
iii) launch a self upgrade, iv) close itself down in session 0.

See "Patch 21 - New service self update code" which fixed your concern.
Should be a quick review, fixed formatting of all javadoc in this patch instead of having to re-upload all of the patches.
Attachment #573531 - Flags: review?(robert.bugzilla)
Comment on attachment 572654 [details] [diff] [review]
Patch 3 - Code for installing the service. v3''.

>diff --git a/toolkit/components/maintenanceservice/serviceinstall.cpp b/toolkit/components/maintenanceservice/serviceinstall.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/serviceinstall.cpp
>...
>+/**
>+ * Installs or upgrades the service for the process we are running as.
"for the process we are running as" is a tad confusing and could be stated clearer.

>+ * If an existing service is already installed, we replace it with ourselves
>+ * if we are newer than what is already installed.
>+ *  
>+ * @upgradeOnly   If TRUE works the same but will not install
If TRUE will only upgrade an existing install

>+ * @return        TRUE if the service was installed/upgraded
>+ */
>+BOOL
>+SvcInstall(BOOL upgradeOnly)
>+{
>+  // Get a handle to the local computer SCM database with full access rights.
>+  nsAutoServiceHandle schSCManager(OpenSCManager(NULL, NULL, 
>+                                                 SC_MANAGER_ALL_ACCESS));
>+  if (!schSCManager) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not open service manager.  (%d)", GetLastError()));
>+    return FALSE;
>+  }
>+
>+  WCHAR modulePath[MAX_PATH + 1];
>+  if(!GetModuleFileNameW(NULL, modulePath, 
>+                         sizeof(modulePath)/sizeof(modulePath[0]))) {
nit: sizeof(modulePath) / sizeof(modulePath[0])

>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not obtain module filename when attempting to "
>+       "install service. (%d)",
>+       GetLastError()));
>+    return FALSE;
>+  }
>+
>+  // Check if we already have an open service
>+  nsAutoServiceHandle schService(OpenServiceW(schSCManager, 
>+                                              SVC_NAME, 
>+                                              SERVICE_ALL_ACCESS));
>+  DWORD lastError = GetLastError();
>+  if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError) {
>+    // The service exists but we couldn't open it
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not open service.  (%d)", GetLastError()));
>+    return FALSE;
>+  } else if(schService) {
nit: break out the else if since the previous block returns early

>+    // The service exists and we opened it
>+    DWORD bytesNeeded;
>+    if (QueryServiceConfigW(schService, NULL, 0, &bytesNeeded) || 
>+        GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could not determine buffer size for query service config.  (%d)", 
>+         GetLastError()));
How is this error condition handled?

>+    }
>+
>+    // Get the service config information, in particular we want the binary 
>+    // path of the service.
>+    nsAutoArrayPtr<char> serviceConfigBuffer = new char[bytesNeeded];
>+    if (!QueryServiceConfigW(schService, 
>+        reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()), 
>+        bytesNeeded, &bytesNeeded)) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could open service but could not query service config.  (%d)", 
>+         GetLastError()));
>+      return FALSE;
>+    }
>+    QUERY_SERVICE_CONFIGW &serviceConfig = 
>+      *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
>+
>+    // Obtain the version information of both files
>+    wchar_t *newServiceBinaryPath = NULL;
>+    DWORD existingMajor, existingMinor, existingAux, existingMaintenance;
>+    DWORD newMajor, newMinor, newAux, newMaintenance;    
>+    errno_t err = 0;
>+    if (err =_get_wpgmptr(&newServiceBinaryPath)) {
nit: space after =

Might be cleaner to use the path to the binary which is already in modulePath.

It isn't clear to me from my brief look on MSDN that _get_wpgmptr will return a long path and if it is at all like GetModuleFileNameW the path will be the path used to execute the binary which under some cases won't be a long path. Might be a good thing to always call GetLongPathNameW on paths so we are gauranteed to be working with a long path.

>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+        ("Could obtain service binary path, errno:  (%d)", 
>+        err));
>+      return FALSE;
>+    }
>+
>+    if(!GetVersionNumberFromPath(serviceConfig.lpBinaryPathName, 
>+                                 existingMajor, existingMinor, 
>+                                 existingAux, existingMaintenance) ||
>+       !GetVersionNumberFromPath(newServiceBinaryPath, newMajor, 
>+                                 newMinor, newAux, newMaintenance)) {
>+      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+             ("Could obtain version number from paths"));
>+      return FALSE;
>+    }
Per previous comment I don't think this should fail when an installed service is missing version info.

>+
>+    schService.reset(); //Explicitly close the handle so we can delete it
>+
>+    // Check if we need to replace the old binary with the new one
>+    if ((existingMajor < newMajor) ||
>+        (existingMajor == newMajor && existingMinor < newMinor) ||
>+        (existingMajor == newMajor && existingMinor == newMinor && 
>+         existingAux < newAux) ||
>+        (existingMajor == newMajor && existingMinor == newMinor && 
>+         existingAux == newAux && existingMaintenance < newMaintenance)) {
>+      if (!SvcUninstall()) {
>+        return FALSE;
>+      }
>+
>+      if (!DeleteFile(serviceConfig.lpBinaryPathName)) {
>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+          ("Could not delete old service binary file.  (%d)", GetLastError()));
>+        return FALSE;
>+      }
>+
>+      if (!CopyFile(newServiceBinaryPath, 
>+                    serviceConfig.lpBinaryPathName, FALSE)) {
>+        PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+          ("Could not overwrite old service binary file.  (%d)", 
>+           GetLastError()));
>+        return FALSE;
The copy failing after a successful delete seems pretty bad. I don't think this would ever happen but please add some "run around with arms flailing" text to the PR_LOG to call this out if my assumption that this should never fail is true. If it isn't true please provide information of when this would happen.

Might be cleaner to do a rename of the old binary, a rename of the new binary, and then a delete of the old binary. Definitely more fallback options this way.

>+      }
>+
>+      // We made a copy of ourselves to the existing location.
>+      // The tmp file (the process of which we are executing right now) will be
>+      // left over.  Attempt to self delete ourselves in 5 seconds since we 
>+      // can't do it while we are running.  If it fails this is not a problem.
>+      DWORD err = DeferredDeletePath(newServiceBinaryPath);
>+      
>+      // Setup the new module path
>+      wcsncpy(modulePath, serviceConfig.lpBinaryPathName, MAX_PATH);
>+      // Fall through so we replace the service
>+    } else {
>+      // We don't need to copy ourselves to the existing location.
>+      // The tmp file (the process of which we are executing right now) will be
>+      // left over.  Attempt to self delete ourselves in 5 seconds since we 
>+      // can't do it while we are running.  If it fails this is not a problem.
>+      DWORD err = DeferredDeletePath(newServiceBinaryPath);
>+      
>+      return TRUE; // nothing to do, we already have a newer service installed
>+    }
>+  } else if (upgradeOnly) {
>+    // The service does not exist and we are upgrading, so don't install it
>+    return TRUE;
>+  }
>+
>+  // Create the service as on demand
>+  schService.own(CreateServiceW(schSCManager  , SVC_NAME, SVC_DISPLAY_NAME,
extra spaces after schSCManager

>+                              SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
>+                              SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
>+                              modulePath, NULL, NULL, NULL, NULL, NULL));
So, in the new install case you aren't renaming the binary prior to install?

>+  if (!schService) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not create Windows service.  (%d)", GetLastError()));
>+    return FALSE;
>+  } 
>+
>+  if (!SetUserAccessServiceDACL(schService)) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not set security ACE on service handle, the service will not be "
>+       "able to be started from unelevated processes.  (%d)", 
>+       GetLastError()));
>+  }
I think both of the above PR_LOG messages should denote that this is a failure we should never see.

>+
>+  return TRUE;
>+}
>+
Regarding patch 2:
- Session 0 comments are addressed in the follow up bug 701103
- Javadoc comments are addressed in patch 24
- Implemented various formatting fixes, some of them I left because they are from ode that is removed in bug 701103.
- Removed sharing access on the work item file, it's not needed, just force of habbit.
- Regarding the comment with a question in it "rs do you think via update status file?", this is removed in a future patch

> Might be a good thing to just have a counter in a pref that gets reset after success and incremented on failure. 
> When the failure count reach X number of failures disable using the service using the pref you added for this purpose.

This is a good idea but I think I prefer the status file.  
The new status in the status file seems safer to me especially since we do the update check post profile init.
Also it takes a bit of time to attempt an update, so if it fails we'd never want to try it a second time in succession.
When the maintenanceservice fails it relaunches the callback app, with a counter it would take a long time for 
any value > 1 going between the callback app and the maintenance service in succession without any user feedback.
So I'd like to keep that with the new status instead.


By the way, once every patch gets an r+, I'll provide a roll up patch of everything that you can look at for one final pass through.
Then I'll do a follow up patch for any final review comments.

I think whatever is left for patch 2 is addressed in future patches.
Attachment #572652 - Attachment is obsolete: true
Attachment #573542 - Flags: review?(robert.bugzilla)
rebased after patch 2 review comments
Attachment #572150 - Attachment is obsolete: true
Attachment #573543 - Flags: review?(robert.bugzilla)
Attachment #572150 - Flags: review?(robert.bugzilla)
Rebased after patch 2 review comments
Attachment #571695 - Attachment is obsolete: true
Attachment #573545 - Flags: review?(robert.bugzilla)
Attachment #571695 - Flags: review?(robert.bugzilla)
Rebased after patch 2 review comments
Attachment #571349 - Attachment is obsolete: true
Attachment #573548 - Flags: review?(robert.bugzilla)
Attachment #571349 - Flags: review?(robert.bugzilla)
(In reply to Brian R. Bondy [:bbondy] from comment #321)
> Created attachment 573542 [details] [diff] [review] [diff] [details] [review]
> Patch 2 - Main service logic. v5.
> 
> Regarding patch 2:
> - Session 0 comments are addressed in the follow up bug 701103
> - Javadoc comments are addressed in patch 24
> - Implemented various formatting fixes, some of them I left because they are
> from ode that is removed in bug 701103.
> - Removed sharing access on the work item file, it's not needed, just force
> of habbit.
> - Regarding the comment with a question in it "rs do you think via update
> status file?", this is removed in a future patch
> 
> > Might be a good thing to just have a counter in a pref that gets reset after success and incremented on failure. 
> > When the failure count reach X number of failures disable using the service using the pref you added for this purpose.
> 
> This is a good idea but I think I prefer the status file.  
This is in conjunction with the status file. After x number of failures which would be reported by the status file the pref to disable the service would be set.
> This is in conjunction with the status file. After x number of failures 
> which would be reported by the status file the pref to disable the service 
> would be set.

I guess I don't understand the pref then.  Is it to stop future upgrades from attempting to use the service forever? 

I think any failure that would happen (for example a bad certificate) would be auto fixed by some update in the future so we should always try to use the service once for each new update.
> This is in conjunction with the status file. After x number of failures 
> which would be reported by the status file the pref to disable the service 
> would be set.

Also there is no negative side effect of attempting to use the service except maybe a second or two. The callback is launched with the new status right away which prompts.  Since the service is all silent work the user just thinks it works the same as before.
It would only do this for specific error codes in the update status file, only after x number of failures, and only for the profile that experienced the failures.
Blocks: 701436
> It would only do this for specific error codes in the update status file, 
> only after x number of failures, and only for the profile that experienced 
> the failures.

OK fair enough, I posted a follow up task to do this to reduce traffic in this ticket: 
Bug 701436 - If maintenance service fails to apply an update X times, never use it again for updates
Should be a quick review if you agree with it.
If not I'll just delete this patch.

I don't see any benefit from using the global scope operator on each Win32 API and I think it's much easier to stay consistent by not doing this convention.
Attachment #573568 - Flags: review?(robert.bugzilla)
- Implemented review comments.
- "> So, in the new install case you aren't renaming the binary prior to install?"
  Correct we only install the file from the nsis maintenanceservice script with maintenanceservice_tmp.exe if the file already exists.
  If the file does not exist, we just install it as maintenanceservice.exe
- Javadoc changes are in patch 24
- global scope :: changes are in patch 25
Attachment #572654 - Attachment is obsolete: true
Attachment #573420 - Attachment is obsolete: true
Attachment #573590 - Flags: review?(robert.bugzilla)
Attachment #572654 - Flags: review?(robert.bugzilla)
Attachment #573420 - Flags: review?(robert.bugzilla)
Rebased after review comments on patch 3
Attachment #573531 - Attachment is obsolete: true
Attachment #573592 - Flags: review?(robert.bugzilla)
Attachment #573531 - Flags: review?(robert.bugzilla)
Comment on attachment 573590 [details] [diff] [review]
Patch 3 - Code for installing the service. v4.

rubber stamping this... I'll verify in the final patch that I do a final once over of.
Attachment #573590 - Flags: review?(robert.bugzilla) → review+
Comment on attachment 573590 [details] [diff] [review]
Patch 3 - Code for installing the service. v4.

I rubber stamped the wrong patch dag nabbit.

You obsoleted patch 3... are you working on a new version?
Attachment #573590 - Flags: review+ → review?(robert.bugzilla)
Comment on attachment 573568 [details] [diff] [review]
Patch 25 - Global scope operator fix

Meant to rubber stamp this one... I'll verify this is correct in the final patch
Attachment #573568 - Flags: review?(robert.bugzilla) → review+
Comment on attachment 573590 [details] [diff] [review]
Patch 3 - Code for installing the service. v4.

> You obsoleted patch 3... are you working on a new version?

Sorry the correct name for this patch is Patch 3. Fixed and remarked r+. Patch 4 is the certificate check code.
Attachment #573590 - Attachment description: Patch 4 - Code for installing the service. v4. → Patch 3 - Code for installing the service. v4.
Attachment #573590 - Flags: review?(robert.bugzilla) → review+
Comment on attachment 573590 [details] [diff] [review]
Patch 3 - Code for installing the service. v4.

Removing r+ just seen your note that you rubber stamped wrong one.
Attachment #573590 - Flags: review+ → review?(robert.bugzilla)
No longer blocks: 701103
- Consolidated all patches into one as per discussion with rs. 
- Although patch 7 RAII helpers is in the consolidated patch, I also left it here until it reaches sr+ from bsmedberg.
- If you want the NSPR logging to work better, you also need to apply the patch from "Bug 699567 - Add cross platform wide string formatting specifier for NSPR logging".  That will likely be pushed soon so didn't include it in this consolidated patch.  In particular if you don't push this any logging of filenames and cmd line strings will not show up in the log.
- Consolidated patch does not include the already pushed patches.

Happy reviewing :)
Attachment #568691 - Attachment is obsolete: true
Attachment #568950 - Attachment is obsolete: true
Attachment #570143 - Attachment is obsolete: true
Attachment #570282 - Attachment is obsolete: true
Attachment #570518 - Attachment is obsolete: true
Attachment #570601 - Attachment is obsolete: true
Attachment #570835 - Attachment is obsolete: true
Attachment #571348 - Attachment is obsolete: true
Attachment #571505 - Attachment is obsolete: true
Attachment #571532 - Attachment is obsolete: true
Attachment #572040 - Attachment is obsolete: true
Attachment #572655 - Attachment is obsolete: true
Attachment #572657 - Attachment is obsolete: true
Attachment #572799 - Attachment is obsolete: true
Attachment #572800 - Attachment is obsolete: true
Attachment #573542 - Attachment is obsolete: true
Attachment #573543 - Attachment is obsolete: true
Attachment #573545 - Attachment is obsolete: true
Attachment #573548 - Attachment is obsolete: true
Attachment #573568 - Attachment is obsolete: true
Attachment #573590 - Attachment is obsolete: true
Attachment #573592 - Attachment is obsolete: true
Attachment #568950 - Flags: review?(robert.bugzilla)
Attachment #570518 - Flags: review?(robert.bugzilla)
Attachment #571348 - Flags: review?(robert.bugzilla)
Attachment #571505 - Flags: review?(robert.bugzilla)
Attachment #572040 - Flags: review?(robert.bugzilla)
Attachment #572655 - Flags: review?(robert.bugzilla)
Attachment #572657 - Flags: review?(robert.bugzilla)
Attachment #572799 - Flags: review?(robert.bugzilla)
Attachment #572800 - Flags: review?(robert.bugzilla)
Attachment #573542 - Flags: review?(robert.bugzilla)
Attachment #573543 - Flags: review?(robert.bugzilla)
Attachment #573545 - Flags: review?(robert.bugzilla)
Attachment #573548 - Flags: review?(robert.bugzilla)
Attachment #573590 - Flags: review?(robert.bugzilla)
Attachment #573592 - Flags: review?(robert.bugzilla)
No longer blocks: 699567
Depends on: 699567
Comment on attachment 573735 [details] [diff] [review]
All maintenance service and related code. v1.

Although the code will change some, I think it is mostly done. I think it would be a good time for a final code review from a security perspective.  Please re-assign the second review Ian if it will be someone else doing it.
Attachment #573735 - Flags: review?(robert.bugzilla)
Attachment #573735 - Flags: review?(imelven)
Rebased for recent push to a makefile on mozilla-central tip.
Attachment #573735 - Attachment is obsolete: true
Attachment #573736 - Flags: review?(robert.bugzilla)
Attachment #573736 - Flags: review?(imelven)
Attachment #573735 - Flags: review?(robert.bugzilla)
Attachment #573735 - Flags: review?(imelven)
Comment on attachment 573736 [details] [diff] [review]
All maintenance service and related code. v1'.

Review of attachment 573736 [details] [diff] [review]:
-----------------------------------------------------------------

Just a thought that we probably want to make sure the binaries involved (namely the service DLL) are marked with all the appropriate security mechanisms
like /GS, /ASLR etc. so we opt into the OS level protections. 

Overall looks good from a security perspective minus the concern about passing arguments to the callback app

::: toolkit/components/maintenanceservice/maintenanceservice.cpp
@@ +218,5 @@
> +    gServiceStopping = TRUE;
> +    if (!PathAppendSafe(stopFilePath, L"stop")) {
> +      TerminateThread(thread, 2);
> +    }
> +    HANDLE stopFile = CreateFile(stopFilePath, GENERIC_READ, 0, 

i thought we were removing the need for the stop file ?

::: toolkit/components/maintenanceservice/registrycertificates.cpp
@@ +63,5 @@
> +  // We use KEY_WOW64_64KEY to always force 64-bit view.
> +  // The user may have both x86 and x64 applications installed
> +  // which each register information.  We need a consistent place
> +  // to put those certificate attributes in and hence why we always
> +  // force the non redirected registry under Wow6432Node.

you might want to add to the comment that the flag is ignored on 32 bit windows

::: toolkit/components/maintenanceservice/serviceinstall.cpp
@@ +211,5 @@
> +      // We made a copy of ourselves to the existing location.
> +      // The tmp file (the process of which we are executing right now) will be
> +      // left over.  Attempt to self delete ourselves in 5 seconds since we 
> +      // can't do it while we are running.  If it fails this is not a problem.
> +      MoveFileEx(newServiceBinaryPath, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);

where does 5 seconds come from ? the documentation says the flag does what it sounds like,
wait until an os reboot.

::: toolkit/components/maintenanceservice/uachelper.cpp
@@ +100,5 @@
> +  return hNewLinkedToken;
> +}
> +
> +/**
> + * Opens a linked token for the specified token.

looks like a c & p from above ?

::: toolkit/components/maintenanceservice/workmonitor.cpp
@@ +109,5 @@
> +  LPVOID environmentBlock = NULL;
> +  LPWSTR cmdLineMinusCallback = MakeCommandLine(min(argcTmp, 4), argvTmp);
> +
> +  // If we're about to start the update process from session 0 on vista
> +  // or later, then we should not show a GUI.

i don't understand how the updater.ini file related to showing a gui - can you elaborate please ?

@@ +130,5 @@
> +    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
> +      ("Process was started... waiting on result.")); 
> +
> +    // Wait for the updater process to finish
> +    WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER);

it looks like if this times out, GetExitCodeProcess will return still active
and update was Successful will correctly return false - then we start the callback app. 
maybe we should consider killing the process if the time out happens ?

@@ +270,5 @@
> +       notifyInfo.FileName));
> +    return TRUE;
> +  }
> +  cmdlineBuffer[cmdLineBufferRead] = L'\0';
> +  cmdlineBuffer[cmdLineBufferRead + 1] = '\0';

L'\0' is 2 bytes so i think you don't want the 2nd one of these (cmdlineBuffer is a char buffer not wchar)

@@ +283,5 @@
> +  LPWSTR* argvTmp = CommandLineToArgvW(cmdlineBufferWide, &argcTmp);
> +
> +  // Validate the certificate of the app the user wants us to start.
> +  // Also check to make sure the certificate is trusted.
> +#ifndef DISABLE_SERVICE_AUTHENTICODE_CHECK

i assume this is for testing ?

@@ +319,5 @@
> +      PR_LOG(gServiceLog, PR_LOG_ALWAYS,
> +        ("Could not write update.status pending-no-service.  Last error: %d", 
> +         GetLastError()));
> +    }
> +    StartCallbackApp(argcTmp, argvTmp, sessionID);

this looks like the callback app takes a whole command line from the work item - so could i create a workitem (since the dir is world writeable) and a spoofed session ID of another user and inject arbitrary arguments to an existing app and have it run in their session ? i was under the impression the callback app was going to be a path to an executable and not allow specifying parameters, can you confirm ?

@@ +502,5 @@
> +    }
> +    return FALSE;
> +  }
> +
> +#ifdef ENABLE_CALLBACK_AUTHENTICODE_CHECK

i assume this is not defined, since we don't have a process for signing nightly builds yet - will it be defined for releases / betas ?

::: toolkit/xre/nsWindowsRestart.cpp
@@ +295,5 @@
> +
> +BOOL
> +PathAppendSafe(LPWSTR base, LPCWSTR extra)
> +{
> +  if (wcslen(base) + wcslen(extra) > MAX_PATH) {

according to some MS documentation i just found, MAX_PATH is supposed to include the terminating NULL, so maybe this should be if (wcslen(base) + wcslen(extra) >= MAX_PATH) ?

the buffers passed in all seem to be MAX_PATH + 1 so this is a bit of a nit, although later in the file MAX_PATH * sizeof(WCHAR) is used as the size to write to the file (i believe these are considered to not necc. be null terminated strings written to the file though IIRC)
Attachment #573736 - Flags: review?(imelven)
Thanks for the review comments Ian!

> i thought we were removing the need for the stop file ?

It's needed.  I use it to wake up the sync call to the directory watching.  
That way I can safely exit that thread withougt blindly killing it on service stop.  
It won't actually stop monitoring unless the flag is set though, so if someone just drops a file that says stop in it won't actually stop.  
Only if the stop service command is issued and the service is shutting down will gServiceStopping be true.   
We could alternatively use the async API for directory watching, but I see no benefit to do that.

> maybe we should consider killing the process if the time out happens ?

That's a good idea, I implemented killing updater.exe if it takes more than 45 minutes.  
It would have still been handled as an error before because the GetExitCodeProcess function call would have failed, but it's better to explicitly handle the timeout case and kill the process.  
We wouldn't want the operation to succeed right after this and then have the service write the pending-no-service status in the status file.


> +#ifndef DISABLE_SERVICE_AUTHENTICODE_CHECK
> i assume this is for testing ?

Yup for testing only, and may be used for some automated tests if we can't get the try builds signed.


> +#ifdef ENABLE_CALLBACK_AUTHENTICODE_CHECK
> i assume this is not defined, since we don't have a process for 
> signing nightly builds yet - will it be defined for releases / betas ?

This is not defined currently and is planned to land on Nightly that way. Please advise if you think that is a big problem. Session ID spoofing would be possible here as you mentioned.  But the app will always be run unelevated.  The callback app is restarting firefox after the update.  If a user started firefox with -P profileName then we want to restart with those same command line params.   I plan to change that ifdef to #ifndef DISABLE_SERVICE_AUTHENTICODE_CHECK once we have nightly builds automatically signed.

> i assume this is not defined, since we don't have a process for signing 
> nightly builds yet - will it be defined for releases / betas ?

If acceptable I'd only like it this way only on Nightly.  As with the cert check on the udpater itself not even on Aurora.

> according to some MS documentation i just found, MAX_PATH is supposed 
> to include the terminating NULL...

I'm pretty sure that sometimes it is and sometimes it's not, so it's best to not make an assumption there.  I always allocated MAX_PATH + 1 to be safe when using any such API.  So yup I changed to "if (wcslen(base) + wcslen(extra) >= MAX_PATH)".  As for why I write only MAX_PATH * sizeof(WCHAR), this is just how I defined the file format of the work items.
Attachment #573736 - Attachment is obsolete: true
Attachment #574292 - Flags: review?(robert.bugzilla)
Attachment #573736 - Flags: review?(robert.bugzilla)
Attachment #574292 - Attachment is patch: true
Attached patch For security review only (obsolete) — Splinter Review
As per email, I extracted the files I think are important for security review.
The full patch is left as is for Robert Strong to review.
Attachment #574298 - Flags: review?(bsmith)
(In reply to Brian R. Bondy [:bbondy] from comment #342)
>
> Thanks for the review comments Ian!

you are very welcome :) 
 
> > i thought we were removing the need for the stop file ?
> 
> It's needed.  I use it to wake up the sync call to the directory watching.  
> That way I can safely exit that thread withougt blindly killing it on
> service stop.  
> It won't actually stop monitoring unless the flag is set though, so if
> someone just drops a file that says stop in it won't actually stop.  
> Only if the stop service command is issued and the service is shutting down
> will gServiceStopping be true.   
> We could alternatively use the async API for directory watching, but I see
> no benefit to do that.

that all sounds fine to me - even if the service is stopped, the next update will attempt to start it again, IIRC - so i don't see any risk here especially
with the points you made above

> > +#ifdef ENABLE_CALLBACK_AUTHENTICODE_CHECK
> > i assume this is not defined, since we don't have a process for 
> > signing nightly builds yet - will it be defined for releases / betas ?
> 
> This is not defined currently and is planned to land on Nightly that way.
> Please advise if you think that is a big problem. Session ID spoofing would
> be possible here as you mentioned.  But the app will always be run
> unelevated.  The callback app is restarting firefox after the update.  If a
> user started firefox with -P profileName then we want to restart with those
> same command line params.   I plan to change that ifdef to #ifndef
> DISABLE_SERVICE_AUTHENTICODE_CHECK once we have nightly builds automatically
> signed.

since we're always restarting firefox.exe (i assume the executable isn't often renamed ?) could we hardcode 'firefox.exe' and then have the path come from the work item - this would solve my concern with running arbitrary executables with an arbitrary command line without requiring signing (although signing is obv. better for non-nightly releases). also re session spoofing - since the callback app is being run unelevated, it may be that the unelevated user token wouldn't have access to the other user's session by default. we need to do some testing to see if the session injection is a practical attack (i'm tracking this). 

> > according to some MS documentation i just found, MAX_PATH is supposed 
> > to include the terminating NULL...
> 
> I'm pretty sure that sometimes it is and sometimes it's not, so it's best to
> not make an assumption there.  I always allocated MAX_PATH + 1 to be safe
> when using any such API.  So yup I changed to "if (wcslen(base) +
> wcslen(extra) >= MAX_PATH)".  As for why I write only MAX_PATH *
> sizeof(WCHAR), this is just how I defined the file format of the work items.

yeah, i agree that it's best to be defensive with this and not rely on the documentation.

thanks !
The preference screen already had a r+ from faaborg.
What wasn't approved yet was hiding this option when the maintenance service is not installed.  Also I wanted to make sure that it's ok that we show the option for both limited user accounts and admin accounts.  I think it is OK to show for both admin and limited user accounts as we will advise people to use lock_pref() if they are concerned about it.
Attachment #574770 - Flags: review?(limi)
Attachment #574770 - Flags: feedback?(robert.bugzilla)
Attachment #574770 - Flags: review?(limi) → ui-review?(limi)
This patch is in addition to the consolidated patch.
I will merge them both into one once the reviews are done.

Changes in this patch:
- Now only show the preference if the maintenance service is installed.
- Fixed PostUpdate proces so it runs now for both admins and limited user acocunts
- Update check is reverted to pre xpcom init again (this change is in base consolidated patch)
- Now do updates via pending and pending-service states. pending-service is used if an update is pending and the service should be used to attempt it.
- Fixed up error handling a bit so updater.exe failed state actually survives and doesn't get overwritten by "pending" by the service.
- Added new pref for max fail count where service won't be reached if it fails too many times.
- Now hide updater.exe via CreateProcess flags and not by deleting updater.ini since it contains the PostUpdate info.
- Other minor cleanups and fixes.
Attachment #574798 - Flags: review?(robert.bugzilla)
This is the same consolidated patch as before (excluding the new patch which aslo has rs to review on).  The only difference is I removed the move of the update check to post XPCOM init diff file.

Please feel free to provide review comments on the old consolidated patch or this one, they are the same.
Attachment #574292 - Attachment is obsolete: true
Attachment #574803 - Flags: review?(robert.bugzilla)
Attachment #574292 - Flags: review?(robert.bugzilla)
Depends on: 702963
No longer blocks: 701436
Comment on attachment 574298 [details] [diff] [review]
For security review only

Review of attachment 574298 [details] [diff] [review]:
-----------------------------------------------------------------

Where are the tests for this patch?
There are no automated tests yet for this patch.
Just wanted to give an update on automated tests.
Ehsan and I just had a call and here is the plan:

Update tests can be broken down into these 6 sections:
1. Old tests (These already exist)
2. Old tests copied and applied to background updates (Ehsan already did this)
3. Old tests copied and applied to background updates using the service (Ehsan volunteered to do these, in the context of his bug)
4. Old tests copied and applied to the service (Ehsan volunteered to do these, in the context of this bug)
5. New tests applied to background updates (Ehsan already did this, in the context of his bug)
6. New tests applied to the service itself (Brian (me) will do this)

So relating to this bug (bug 481815), Ehsan will attach a new patch for #4, and I will attach a new patch for #6 when done. 

How the service will be tested:
Testing through the service is hard because you need to have elevated permissions to install the service.  Also the service does things with user tokens that only the SYSTEM account is allowed to do.

Ehsan talked to RelEng about an idea he had to always have a base copy of the service installed on the machines that would be used to install the version of the service that needs to be tested.  This seems to be the way we will proceed with testing the service itself.
Depends on: 594474
There's also new tests for background updates in presence of the service.  I'll work on this as well.

I will update about our plans after I talk with RelEng again today.
I talked with Brian and bhearsum from RelEng about this, and we're going to go with this proposal for automated testing infrastructure: <https://wiki.mozilla.org/Silent_Update_OS_Dialogs/Automated_testing>.

I filed bug 704578 for this.
Depends on: 704578
Depends on: 704591
No longer depends on: 594474
Comment on attachment 574770 [details]
Prefs with and without the maint service installed

Looks good to me.
Attachment #574770 - Flags: ui-review?(limi) → ui-review+
Merged a couple of fixes from ehsan.
- Now using file share all flags again on the work item file, this was recently changed to no sharing. It causes problems with the rename not closing the handle before the service can open it.
- couple of instances of PENDING_NO_SERVICE needed to be changed to PENDING_SERVICE.
- About dialog had to check for "pending-service"
Attachment #574798 - Attachment is obsolete: true
Attachment #576402 - Flags: review?(robert.bugzilla)
Attachment #574798 - Flags: review?(robert.bugzilla)
Attachment #574770 - Flags: feedback?(robert.bugzilla) → feedback+
Comment on attachment 574803 [details] [diff] [review]
All maintenance service and related code. v2'.

>diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
>--- a/browser/installer/package-manifest.in
>+++ b/browser/installer/package-manifest.in
>@@ -28,16 +28,17 @@
> @BINPATH@/defaults/profile/mimeTypes.rdf
> @BINPATH@/defaults/profile/chrome/*
> @BINPATH@/update.locale
> @BINPATH@/updater.ini
> @BINPATH@/dictionaries/*
> @BINPATH@/hyphenation/*
> #ifdef XP_WIN32
> @BINPATH@/uninstall/helper.exe
>+@BINPATH@/maintenanceservice_installer.exe
nit: Just place this after maintenanceservice.exe so they are all in one place

> #endif
> 
> [xpcom]
> @BINPATH@/dependentlibs.list
> #ifndef MOZ_STATIC_JS
> @BINPATH@/@DLL_PREFIX@mozjs@DLL_SUFFIX@
> #endif
> @BINPATH@/@DLL_PREFIX@plc4@DLL_SUFFIX@
>@@ -519,16 +520,22 @@ bin/libfreebl_32int64_3.so
> ; [Updater]
> ;
> #ifdef XP_MACOSX
> @BINPATH@/updater.app/
> #else
> @BINPATH@/updater@BIN_SUFFIX@
> #endif
> 
>+; [MaintenanceService]
>+;
>+#ifdef XP_WIN32
>+@BINPATH@/maintenanceservice.exe
>+#endif
>+

>diff --git a/toolkit/components/maintenanceservice/Makefile.in b/toolkit/components/maintenanceservice/Makefile.in
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/Makefile.in
>@@ -0,0 +1,103 @@
>+
>+DEPTH     = ../../..
>+topsrcdir = @top_srcdir@
>+srcdir    = @srcdir@
>+VPATH     = @srcdir@
>+
>+include $(DEPTH)/config/autoconf.mk
>+
>+CPPSRCS = \
>+  maintenanceservice.cpp \
>+  serviceinstall.cpp \
>+  workmonitor.cpp \
>+  certificatecheck.cpp \
>+  servicebase.cpp \
>+  registrycertificates.cpp \
>+  uachelper.cpp \
>+  pathhash.cpp \
>+  $(NULL)
>+
>+ifneq ($(OS_TARGET),Android)
nit: not necessary since this Makefile is already excluded for Android in the parent dir. I'd prefer to keep unnecessary cruft out so people don't scratch their heads as to why it is there. 

>+PROGRAM = maintenanceservice$(BIN_SUFFIX)
>+DIST_PROGRAM = maintenanceservice$(BIN_SUFFIX)
>+
>+# Don't link the maintenanceservice against libmozutils. See bug 687139
>+MOZ_UTILS_LDFLAGS =
>+MOZ_UTILS_PROGRAM_LDFLAGS =
>+endif
>+
>+LIBS += \
>+  $(NSPR_LIBS) \
>+  $(NULL)
>+
>+ifeq ($(OS_ARCH),WINNT)
same here

>+USE_STATIC_LIBS = 1
>+HAVE_PROGRESSUI = 1
>+RCINCLUDE = maintenanceservice.rc
>+
>+OS_LIBS += $(call EXPAND_LIBNAME,comctl32 ws2_32 shell32)
>+DEFINES += -DUNICODE -D_UNICODE
>+ifndef GNU_CC
>+RCFLAGS += -I$(srcdir)
>+else
>+RCFLAGS += --include-dir $(srcdir)
>+endif
>+
>+endif
>+
>+ifndef MOZ_WINCONSOLE
>+ifdef MOZ_DEBUG
>+MOZ_WINCONSOLE = 1
>+else
>+MOZ_WINCONSOLE = 0
>+endif
>+endif
>+
>+include $(topsrcdir)/config/rules.mk
>+
>+DEFINES += -DNS_NO_XPCOM
>+
>+ifdef _MSC_VER
>+WIN32_EXE_LDFLAGS += -ENTRY:wmainCRTStartup
>+endif
>+
>+ifeq ($(OS_ARCH),WINNT)
same here

>+# Pick up nsWindowsRestart.cpp
>+LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/xre
>+endif

>diff --git a/toolkit/components/maintenanceservice/maintenanceservice.ico b/toolkit/components/maintenanceservice/maintenanceservice.ico
>new file mode 100644
>index 0000000000000000000000000000000000000000..48457029d6aa5f090e5964e22d2a580017e31b7b
Do we really care if the service has an icon? Many (most?) don't.

>diff --git a/toolkit/components/maintenanceservice/serviceinstall.cpp b/toolkit/components/maintenanceservice/serviceinstall.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/serviceinstall.cpp
>@@ -0,0 +1,430 @@

>+/**
>+ * Uninstalls the Maintenance service.
>+ *
>+ * @return TRUE if successful.
>+ */
>+BOOL
>+SvcUninstall()
>+{
>+  // Get a handle to the local computer SCM database with full access rights.
>+  nsAutoServiceHandle schSCManager(OpenSCManager(NULL, NULL, 
>+                                                 SC_MANAGER_ALL_ACCESS));
>+  if (!schSCManager) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not open service manager.  (%d)", GetLastError()));
>+    return FALSE;
>+  }
>+
>+  // Open the service
>+  nsAutoServiceHandle schService(OpenServiceW(schSCManager, SVC_NAME, 
>+                                              SERVICE_ALL_ACCESS));
>+  if (!schService) {
>+    PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+      ("Could not open service.  (%d)", GetLastError()));
>+    return FALSE;
>+  } 
>+
>+  //Stop the service so it deletes faster and so the uninstaller
>+  // can actually delete its EXE.
>+  DWORD totalWaitTime = 0;
>+  SERVICE_STATUS status;
>+  static const int maxWaitTime = 1000 * 60; // Never wait more than a minute
>+  if (ControlService(schService, SERVICE_CONTROL_STOP, &status)) {
>+    do {
>+      Sleep(status.dwWaitHint);
>+      totalWaitTime += (status.dwWaitHint + 10);
>+      if (status.dwCurrentState == SERVICE_STOPPED) {
>+        break;
>+      } else if (totalWaitTime > maxWaitTime) {
>+        break;
>+      }
>+    } while (QueryServiceStatus(schService, &status));
>+  }
>+
>+  // Delete the service or mark it for deletion
>+  BOOL deleted = DeleteService(schService);
>+  if(!deleted) {
>+    deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE);
I recall ERROR_SERVICE_MARKED_FOR_DELETE preventing the install of a service with the same name back in Win2K days. Have you / can you verify what happens in this case? Might be a good thing to verify on Win XP that the behavior is the same.

On this same note what do you think about not installing the service on Win2K? I suggest this since support for Win2K will likely go away in the release immediately following the installation of the service.

>+  }
>+
>+  return deleted;
>+}
>+

>diff --git a/toolkit/components/maintenanceservice/workmonitor.cpp b/toolkit/components/maintenanceservice/workmonitor.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
>@@ -0,0 +1,561 @@
>...
>+// Wait 45 minutes for an update operation to run at most.
>+// Updates usually take less than a minute so this seems like a 
>+// significantly large and safe amount of time to wait.
>+static const int TIME_TO_WAIT_ON_UPDATER = 45 * 60 * 1000;
This is crazy big. ;)

I can't even imagine an update that takes 5 minutes much less 45 minutes. What do you think?

>...
>+StartUpdateProcess(LPCWSTR appToStart, 
>+                   LPCWSTR workingDir, 
>+                   int argcTmp,
>+                   LPWSTR *argvTmp,
>+                   DWORD callbackSessionID = 0)
>+{
>+  BOOL processStarted = FALSE;
>+  DWORD myProcessID = GetCurrentProcessId();
>+  DWORD mySessionID = 0;
>+  ProcessIdToSessionId(myProcessID, &mySessionID);
>+
>+  STARTUPINFO si = {0};
>+  si.cb = sizeof(STARTUPINFO);
>+  si.lpDesktop = L"winsta0\\Default";
>+  PROCESS_INFORMATION pi = {0};
>+  nsAutoHandle elevatedToken, unelevatedToken;
>+
>+  PR_LOG(gServiceLog, PR_LOG_ALWAYS,
>+    ("Starting process in an elevated session.  Service "
>+     "session ID: %d; Requested callback session ID: %d", 
>+     mySessionID, callbackSessionID));
>+
>+  // The updater command line is of the form:
>+  // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]]
>+  // So update callback-dir is the 4th, callback-path is the 5th and its args 
>+  // are the 6th index.  So that we can execute the callback out of line we
>+  // won't call updater.exe with those callback args and we will manage the
>+  // callback ourselves.
>+  LPVOID environmentBlock = NULL;
>+  LPWSTR cmdLineMinusCallback = MakeCommandLine(min(argcTmp, 4), argvTmp);
>+
>+  // If we're about to start the update process from session 0 on Vista
>+  // or later, then we should not show a GUI.  One way to disable the
>+  // updater.exe GUI is to delete the updater.ini file.
>+  if (UACHelper::IsVistaOrLater() && argcTmp >= 2 ) {
>+    si.lpDesktop = L"";
>+    WCHAR updaterINI[MAX_PATH + 1];
>+    wcscpy(updaterINI, argvTmp[1]);
>+    if (PathAppendSafe(updaterINI, L"updater.ini")) {
>+      DeleteFileW(updaterINI);
>+    }
Though this has changed somewhat in the next patch but I don't think we want different behavior regarding showing or not showing the updater progress based on OS version.

I wonder if elevation type or if the user is admin is interesting / needed if we always use session 0 to apply the update? Could you provide some details?

>+  }
>+
>+  processStarted = CreateProcessW(appToStart, cmdLineMinusCallback, 
>+                                  NULL, NULL, FALSE, 
>+                                  CREATE_DEFAULT_ERROR_MODE | 
>+                                  CREATE_UNICODE_ENVIRONMENT, 
>+                                  NULL, workingDir, &si, &pi);
Comment on attachment 574803 [details] [diff] [review]
All maintenance service and related code. v2'.

>diff --git a/other-licenses/nsis/Contrib/ServicesHelper/Services.cpp b/other-licenses/nsis/Contrib/ServicesHelper/Services.cpp
>new file mode 100644
Though this code still needs review if you publish it on the NSIS web site we don't have to take this code into the repo.

>--- /dev/null
>+++ b/other-licenses/nsis/Contrib/ServicesHelper/Services.cpp
>@@ -0,0 +1,272 @@
>...
>+/**
>+ * Determines a unique registry path from a file or directory path
>+ * 
>+ * @param  stacktop  A pointer to the top of the stack
>+ * @param  variables A pointer to the NSIS variables 
>+ * @return The unique registry path or an empty string on error
>+ */
>+extern "C" void __declspec(dllexport)
>+PathToUniqueRegistryPath(HWND hwndParent, int string_size, 
>+                         TCHAR *variables, stack_t **stacktop, 
>+                         void *extra)
>+{
>+  TCHAR tmp[MAX_PATH] = { L'\0' };
>+  WCHAR installBasePath[MAX_PATH] = { '\0' };
>+  popstring(stacktop, tmp, MAX_PATH);
>+
>+#if !defined(UNICODE)
>+    MultiByteToWideChar(CP_ACP, 0, tmp, -1, installBasePath, MAX_PATH);
>+#else
>+    wcscpy(installBasePath, tmp);
>+#endif
>+
>+  WCHAR registryPath[MAX_PATH + 1] = { '\0' };
>+  if (CalculateRegistryPathFromFilePath(installBasePath, registryPath)) {
This is kind of a bummer... guess we'll just have a Mozilla only plugin.

btw: thanks for compiling this down to a small size... every bit helps.
Though it looks fine I'm still slightly concerned about locale packaging... it tends to bite us in the butt whenever we make changes. Have you tried local repackaging yet? If not, I can over the U.S. holiday.
> Though it looks fine I'm still slightly concerned about locale 
> packaging... it tends to bite us in the butt whenever we make 
> changes. Have you tried local repackaging yet? If not, I 
> can over the U.S. holiday.

Is this a normal part of try builds? If so last time I tried try builds they were passing.  If not please let me know the command to run and I can make sure it works. I don't want to eat up any of your holiday.
This is the first step towards having tests which can use the maintenance service to test the behavior of the updater.  This patch adds one test equivalent to test_0110_general.js which uses the maintenance service to launch the updater, exactly what happens when an update is initiated from Firefox with the presence of the service.  It also includes all of the test infrastructure service necessary to make this work.

The basic idea is that we stage the update root dir and the app dir, set update.status to "pending-service", then launch firefox.exe -process-updates, with two environment variables set to override the update root dir and the app dir from their normal values.  Firefox will then get in touch with the service, and the service will launch the updater.exe process.  We'll keep watching update.status for a success code, and as soon as we get that code, we proceed to verify the results of the update the same way that test_0110_general.js does.

This patch should apply on top of Brian's patches.  Brian, I'd appreciate if you can take a look at this and let me know what you think.  For those who are curious to follow the development of more tests in this category can watch this branch: <https://github.com/ehsan/mozilla-central/tree/nouac-tests>.
No, it isn't. The steps are a tad convoluted and I tend to get them wrong a few times before I get them right so I'll just do this over the weekend unless Axel or someone provide the details. If no one does provide details I'll post the process I used in this bug after I get them right.
Ehsan: Great update, thanks!  I'll take a look as soon as possible and post

Robert: Thanks, I think this can wait until next week if you're busy.  Once you post the steps I will verify them and I can create a wiki page about it.
I think there might already be a wiki page for it where I got the details from originally though I haven't found it for quite some time.

>diff --git a/toolkit/xre/nsWindowsRestart.cpp b/toolkit/xre/nsWindowsRestart.cpp
>--- a/toolkit/xre/nsWindowsRestart.cpp
>+++ b/toolkit/xre/nsWindowsRestart.cpp
>...
>+BOOL
>+GetUpdateDirectoryPath(WCHAR *path) 
>+{
>+  HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, 
>+    SHGFP_TYPE_CURRENT, path);
>+  if (FAILED(hr)) {
>+    return FALSE;
>+  }
>+  if (!PathAppendSafe(path, L"Mozilla")) {
>+    return FALSE;
>+  }
>+  // The directory should already be created from the installer, but
>+  // just to be safe in case someone deletes.
>+  CreateDirectoryW(path, NULL);
The installer creates this directory so all users can write to it while this doesn't. :(

>+/**
>+ * Sets update.status to pending-no-service so that the next startup will
>+ * not use the service and attempt an update the old way.
>+ *
>+ * @param  updateDirPath    The path of the update directory
>+ * @return TRUE if successful
>+ */
>+BOOL
>+WriteStatusPendingNoService(LPCWSTR updateDirPath)
Could this be moved to nsUpdateDriver.cpp?

>+{
>+  WCHAR updateStatusFilePath[MAX_PATH + 1];
>+  wcscpy(updateStatusFilePath, updateDirPath);
>+  if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
>+    return FALSE;
>+  }
>+
>+  const char pendingNoService[] = "pending-no-service";
>+  nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0, 
>+                                      NULL, CREATE_ALWAYS, 0, NULL));
>+  if (statusFile == INVALID_HANDLE_VALUE) {
>+    return FALSE;
>+  }
>+
>+  DWORD wrote;
>+  BOOL ok = WriteFile(statusFile, pendingNoService, 
>+                      sizeof(pendingNoService) - 1, &wrote, NULL); 
>+  return ok && (wrote == sizeof(pendingNoService) - 1);
>+}
Attachment #574770 - Attachment is obsolete: true
Comment on attachment 574803 [details] [diff] [review]
All maintenance service and related code. v2'.

>diff --git a/browser/installer/windows/Makefile.in b/browser/installer/windows/Makefile.in
>--- a/browser/installer/windows/Makefile.in
>+++ b/browser/installer/windows/Makefile.in
>diff --git a/browser/installer/windows/nsis/installer.nsi b/browser/installer/windows/nsis/installer.nsi
>--- a/browser/installer/windows/nsis/installer.nsi
>+++ b/browser/installer/windows/nsis/installer.nsi
>@@ -35,16 +36,17 @@
> # ***** END LICENSE BLOCK *****
> 
> # Required Plugins:
> # AppAssocReg   http://nsis.sourceforge.net/Application_Association_Registration_plug-in
> # ApplicationID http://nsis.sourceforge.net/ApplicationID_plug-in
> # CityHash      http://mxr.mozilla.org/mozilla-central/source/other-licenses/nsis/Contrib/CityHash
> # ShellLink     http://nsis.sourceforge.net/ShellLink_plug-in
> # UAC           http://nsis.sourceforge.net/UAC_plug-in
>+# ServicesHelper
Realign the above and add a note about this being a Mozilla specific plugin that is located in other-licenses.

>@@ -477,16 +506,30 @@ Section "-Application" APP_IDX
>         ; add the log entry without the path since there is no simple way to
>         ; know the correct full path.
>         ${LogMsg} "Added Quick Launch Shortcut: ${BrandFullName}.lnk"
>         GetFunctionAddress $0 AddQuickLaunchShortcut
>         UAC::ExecCodeSegment $0
>       ${EndIf}
>     ${EndUnless}
>   ${EndIf}
>+

Add a comment that this has to be done at the end so the correct registry view is maintained during the installation
>+  ; Allow main Mozilla cert information for updates
>+  ServicesHelper::PathToUniqueRegistryPath "$INSTDIR"
>+  Pop $MaintCertKey
>+  ${If} $MaintCertKey != ""
>+    ; We always use the 64bit registry for certs
>+    ; This call is ignored on 32-bit systems.
>+    SetRegView 64
>+    WriteRegStr HKLM "$MaintCertKey\0" "name" "Mozilla Corporation"
>+    WriteRegStr HKLM "$MaintCertKey\0" "issuer" "Thawte Code Signing CA - G2"
>+    WriteRegStr HKLM "$MaintCertKey\0" "programName" ""
>+    WriteRegStr HKLM "$MaintCertKey\0" "publisherLink" ""
>+    WriteRegStr HKLM "$MaintCertKey\0" "moreInfoLink" "http://www.mozilla.com"
>+  ${EndIf} 
Make a note that we can't have a fallback certificate in this instance here and in other places as appropriate.


> SectionEnd
> 
> ; Cleanup operations to perform at the end of the installation.
> Section "-InstallEndCleanup"
>   SetDetailsPrint both
>   DetailPrint "$(STATUS_CLEANUP)"
>   SetDetailsPrint none
> 
>@@ -794,16 +837,52 @@ Function leaveShortcuts
>     ${MUI_INSTALLOPTIONS_READ} $AddQuickLaunchSC "shortcuts.ini" "Field 4" "State"
>   ${EndUnless}
> 
>   ${If} $InstallType == ${INSTALLTYPE_CUSTOM}
>     Call CheckExistingInstall
>   ${EndIf}
> FunctionEnd
> 
>+Function preComponents
>+  ; Don't show the custom components page if the
>+  ; user is not an admin
>+  Call IsUserAdmin
>+  Pop $R9
>+  ${If} $R9 != "true"
>+    Abort
>+  ${EndIf}
>+
>+  ; If the service already exists, don't show this page
>+  ; We will always install again (which will upgrade)
>+  ; as long as the user is admin
>+  ServicesHelper::IsInstalled "MozillaMaintenance"
>+  Pop $R9
>+  ${If} $R9 == 1
>+    ; The service already exists so don't show this page.
>+    Abort
>+  ${EndIf}
>+
>+  StrCpy $PageName "Components"
Sent an email to Blake regarding this and cc'd you.
Comment on attachment 574803 [details] [diff] [review]
All maintenance service and related code. v2'.

>diff --git a/browser/installer/windows/nsis/maintenanceservice_installer.nsi b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
>new file mode 100644
>--- /dev/null
>+++ b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
>@@ -0,0 +1,264 @@
>...
>+# Required Plugins:
>+# ServicesHelper
>+
>+; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
>+!verbose 3
>+
>+; 7-Zip provides better compression than the lzma from NSIS so we add the files
>+; uncompressed and use 7-Zip to create a SFX archive of it
>+SetDatablockOptimize on
>+SetCompress off
>+CRCCheck on
>+
>+RequestExecutionLevel admin
>+!addplugindir ./
>+
>+; Variables
>+Var PageName
>+Var TempMaintServiceName
>+
>+; Modenr UI
>+!include "MUI2.nsh"
>+
>+
>+; Other included files may depend upon these includes!
>+; The following includes are provided by NSIS.
>+!include FileFunc.nsh
>+!include LogicLib.nsh
>+!include MUI.nsh
>+!include WinMessages.nsh
>+!include WinVer.nsh
>+!include WordFunc.nsh
>+
>+!insertmacro GetOptions
>+!insertmacro GetParameters
>+!insertmacro GetSize
>+!insertmacro StrFilter
>+!insertmacro WordFind
>+!insertmacro WordReplace
>+
>+!define CompanyName "Mozilla Corporation"
We need to think about how 3rd parties can use this.

>+
>+; The following includes are custom.
>+!include defines.nsi
>+; We keep defines.nsi defined so that we get other things like 
>+; the version number, but we redefine BrandFullName
>+!define MaintFullName "Mozilla Maintenance Service"
>+!undef BrandFullName
>+!define BrandFullName "${MaintFullName}"
>+
>+!include common.nsh
>+!include locales.nsi
>+
>+; Must be inserted before other macros that use logging
>+!insertmacro _LoggingCommon
>+
>+!insertmacro AddDDEHandlerValues
>+!insertmacro ChangeMUIHeaderImage
>+!insertmacro CheckForFilesInUse
>+!insertmacro CleanUpdatesDir
>+!insertmacro CopyFilesFromDir
>+!insertmacro CreateRegKey
>+!insertmacro GetPathFromString
>+!insertmacro GetParent
>+!insertmacro IsHandlerForInstallDir
>+!insertmacro IsPinnedToTaskBar
>+!insertmacro LogDesktopShortcut
>+!insertmacro LogQuickLaunchShortcut
>+!insertmacro LogStartMenuShortcut
>+!insertmacro ManualCloseAppPrompt
>+!insertmacro PinnedToStartMenuLnkCount
>+!insertmacro RegCleanAppHandler
>+!insertmacro RegCleanMain
>+!insertmacro RegCleanUninstall
>+!insertmacro SetAppLSPCategories
>+!insertmacro SetBrandNameVars
>+!insertmacro UpdateShortcutAppModelIDs
>+!insertmacro WriteRegStr2
>+!insertmacro WriteRegDWORD2
>+!insertmacro CheckIfRegistryKeyExists
Looks like you have a lot of cruft if as I thought the maintenance service installer is going to be silent.

>+Name "${MaintFullName}"
>+OutFile "maintenanceservice_installer.exe"
>+InstallDir "$PROGRAMFILES\${MaintFullName}"
>+
>+; Get installation folder from registry if available
>+InstallDirRegKey HKCU "Software\Mozilla\MaintenanceService" ""
>+
>+SetOverwrite on
>+
>+!define MaintUninstallKey \
>+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService"
>+
>+!ifdef HAVE_64BIT_OS
>+  InstallDir "$PROGRAMFILES64\${MaintFullName}\"
>+!else
>+  InstallDir "$PROGRAMFILES32\${MaintFullName}\"
>+!endif
>+ShowInstDetails nevershow
>+
>+################################################################################
>+# Modern User Interface - MUI
>+
>+!define MUI_ICON setup.ico
>+!define MUI_UNICON setup.ico
>+!define MUI_WELCOMEPAGE_TITLE_3LINES
>+!define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
>+
>+;Interface Settings
>+!define MUI_ABORTWARNING
>+
>+; Uninstaller Pages
>+!define MUI_PAGE_CUSTOMFUNCTION_PRE un.preWelcome
>+!insertmacro MUI_UNPAGE_WELCOME
>+!insertmacro MUI_UNPAGE_CONFIRM
>+!insertmacro MUI_UNPAGE_INSTFILES
>+!insertmacro MUI_UNPAGE_FINISH
>+
>+################################################################################
>+# Language
>+
>+!insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
>+!verbose push
>+!verbose 3
>+!include "overrideLocale.nsh"
>+!include "customLocale.nsh"
>+!verbose pop
>+
>+Function .onInit
>+  SetSilent silent
>+FunctionEnd
>+
>+Function un.onInit
>+  StrCpy $BrandFullNameDA "${MaintFullName}"
>+FunctionEnd
>+
>+Function un.preWelcome
>+  StrCpy $PageName "Welcome"
>+  ${If} ${FileExists} "$EXEDIR\core\distribution\modern-wizard.bmp"
>+    Delete "$PLUGINSDIR\modern-wizard.bmp"
>+    CopyFiles /SILENT "$EXEDIR\core\distribution\modern-wizard.bmp" \
>+              "$PLUGINSDIR\modern-wizard.bmp"
>+  ${EndIf}
>+FunctionEnd
>+
>+Section "MaintenanceService"
>+  AllowSkipFiles off
>+
>+  ${LogHeader} "Installing Main Files"
>+
>+  CreateDirectory $INSTDIR
>+  SetOutPath $INSTDIR
>+
>+  ; Stop the maintenance service so we can overwrite any
>+  ; binaries that it uses.
>+  ; 1 for wait for file release, and 30 second timeout
>+  ServicesHelper::Stop "MozillaMaintenance"
>+
>+  ; If we don't have maintenanceservice.exe already installed
>+  ; then keep that name.  If we do use maintenanceservice_tmp.exe
>+  ; which will auto install itself when you call it with the install parameter.
>+  StrCpy $TempMaintServiceName "maintenanceservice.exe"
>+  IfFileExists "$INSTDIR\maintenanceservice.exe" 0 skipAlreadyExists
>+    StrCpy $TempMaintServiceName "maintenanceservice_tmp.exe"
>+  skipAlreadyExists:
>+
>+  ; We always write out a copy and then decide whether to install it or 
>+  ; not via calling its 'install' cmdline which works by version comparison.
>+  CopyFiles "$EXEDIR\nspr4.dll" "$INSTDIR"
>+  CopyFiles "$EXEDIR\maintenanceservice.exe" "$INSTDIR\$TempMaintServiceName"
>+
>+  ; Install the application updater service.
>+  ; If a service already exists, the command line parameter will stop the
>+  ; service and only install itself if it is newer than the already installed
>+  ; service.  If successful it will remove the old maintenanceservice.exe
>+  ; and replace it with maintenanceservice_tmp.exe.
>+  ClearErrors
>+  ${GetParameters} $0
>+  ${GetOptions} "$0" "/Upgrade" $0
>+  ${Unless} ${Errors}
>+    ; The upgrade cmdline is the same as install except
>+    ; It will fail if the service isn't already installed.
>+    nsExec::Exec '"$INSTDIR\$TempMaintServiceName" upgrade'
>+  ${Else}
>+    nsExec::Exec '"$INSTDIR\$TempMaintServiceName" install'
>+  ${EndIf}
>+
>+  ${GetLongPath} "$INSTDIR" $8
>+  WriteRegStr HKLM "${MaintUninstallKey}" "DisplayName" "${MaintFullName}"
>+  WriteRegStr HKLM "${MaintUninstallKey}" "UninstallString" \
>+                   '"$INSTDIR\uninstall.exe"'
>+  WriteRegStr HKLM "${MaintUninstallKey}" "DisplayIcon" "$INSTDIR\maintenanceservice.exe,0"
>+  WriteRegStr HKLM "${MaintUninstallKey}" "DisplayVersion" "${AppVersion}"
>+  WriteRegStr HKLM "${MaintUninstallKey}" "Publisher" "Mozilla"
>+  WriteRegStr HKLM "${MaintUninstallKey}" "Comments" \
>+                   "${BrandFullName} ${AppVersion} (${ARCH} ${AB_CD})"
>+  ${GetSize} "$8" "/S=0K" $R2 $R3 $R4
>+  WriteRegDWORD HKLM "${MaintUninstallKey}" "EstimatedSize" $R2 
>+
>+  WriteUninstaller "$INSTDIR\Uninstall.exe"
>+
>+  ; Write out that a maintenance service was attempted.
>+  ; We do this because on upgrades we will check this value and we only
>+  ; want to install once on the first upgrade to maintenance service.
>+  ; Since the Maintenance service can be installed either x86 or x64,
>+  ; always use the 64-bit registry for checking if an attempt was made.
>+  SetRegView 64
Add a comment that this has to be done at the end so the correct registry view is maintained during the installation

>+  WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Attempted" 1
>+
>+  # Make the update directory read/write for all users.
>+  # This is to avoid permission problems from multiple users doing updates.
>+  SetShellVarContext all
>+  CreateDirectory "$APPDATA\Mozilla\updates"
>+  AccessControl::GrantOnFile \
>+    "$APPDATA\Mozilla\updates" "(BU)" "FullAccess"
>+SectionEnd
>+
>+Section "Uninstall"
>+  ; Delete the service so that no updates will be attempted
>+  nsExec::Exec '"$INSTDIR\maintenanceservice.exe" uninstall'
>+
>+  Delete "$INSTDIR\maintenanceservice.exe"
>+  Delete "$INSTDIR\maintenanceservice_tmp.exe"
>+  Delete "$INSTDIR\nspr4.dll"
>+  Delete "$INSTDIR\Uninstall.exe"
>+  RMDir "$INSTDIR"
Perhaps /REBOOTOK?

>diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh
>--- a/browser/installer/windows/nsis/shared.nsh
>+++ b/browser/installer/windows/nsis/shared.nsh
>@@ -100,16 +101,33 @@
>   ${FixClassKeys}
>   ${SetUninstallKeys}
> 
>   ; Remove files that may be left behind by the application in the
>   ; VirtualStore directory.
>   ${CleanVirtualStore}
> 
>   ${RemoveDeprecatedFiles}
>+
>+  ; We check to see if the maintenance service install was already attempted.
>+  ; Since the Maintenance service can be installed either x86 or x64,
>+  ; always use the 64-bit registry for checking if an attempt was made.
>+  SetRegView 64
Add a comment that this has to be done at the end so the correct registry view is maintained during the installation

I suspect a is admin check is needed here

>+  ReadRegDWORD $5 HKLM "Software\Mozilla\MaintenanceService" "Attempted"
>+  ${If} $5 == ""
>+    ; An install of maintenance service was never attempted.
>+    ; We call ExecShell (which is ShellExecute) with the verb "runas"
>+    ; to ask for elevation if the user isn't already elevated.  If the user 
>+    ; is already elevated it will just launch the program.
>+    ExecShell "runas" "$INSTDIR\maintenanceservice_installer.exe"
>+  ${Else}
>+    ; The maintenance service is already installed.
>+    ; Do nothing, the maintenance service will launch the 
>+    ; maintenanceservice_installer.exe /Upgrade itself to do the self update. 
>+  ${EndIf}
Comment on attachment 574803 [details] [diff] [review]
All maintenance service and related code. v2'.

The certificatecheck.cpp code looks good but I definitely want bsmith and / or imelven to review that as well if they haven't already.

One more look I think... I'm going to review the incremental change patch right now. Please include both in one final patch.
Attachment #574803 - Flags: review?(robert.bugzilla) → review-
> Make a note that we can't have a fallback certificate 
> in this instance here and in other places as appropriate.

We could add a second certificate info to $MaintCertKey\1 or any other subfolder name and it would work btw.
I thought that was for the signed binary files.
> I'm going to review the incremental change patch right now. Please include both in one final patch.

Awesome! Yup I'll include both in a merged view.

> One more look I think... I'm going to review the incremental 
> change patch right now. Please include both in one final patch.

Ian has done one pass on it already but one of them will review it again before we're ready to push.


> I suspect a is admin check is needed here

The maintenance service installer will always be run as admin and never as a limited user or non elevated user.

> I thought that was for the signed binary files.

I'm not sure if you can have more than one authenticode sign on an exe.  In any case it allows diff certificates currently but we can disable that to only allow 1 certificate if it matters.
(In reply to Brian R. Bondy [:bbondy] from comment #368)
> > I suspect a is admin check is needed here
> 
> The maintenance service installer will always be run as admin and never as a
> limited user or non elevated user.
PostUpdate won't always run as admin though. Is it going to prompt to elevate in that case? I think I'm ok with that.

> 
> > I thought that was for the signed binary files.
> 
> I'm not sure if you can have more than one authenticode sign on an exe.  In
> any case it allows diff certificates currently but we can disable that to
> only allow 1 certificate if it matters.
No need to change it but I wanted the comment to call it out so anyone not familiar would know.
Comment on attachment 576402 [details] [diff] [review]
Incremental changes on the big base patch. v2.

>diff --git a/browser/components/preferences/advanced.js b/browser/components/preferences/advanced.js
>--- a/browser/components/preferences/advanced.js
>+++ b/browser/components/preferences/advanced.js
>@@ -507,16 +507,34 @@ var gAdvancedPane = {
>     // A locked pref is sufficient to disable the radiogroup.
>     radiogroup.disabled = !canCheck || enabledPref.locked || autoPref.locked;
> 
>     var modePref = document.getElementById("app.update.mode");
>     var warnIncompatible = document.getElementById("warnIncompatible");
>     // the warnIncompatible checkbox value is set by readAddonWarn
>     warnIncompatible.disabled = radiogroup.disabled || modePref.locked ||
>                                 !enabledPref.value || !autoPref.value;
>+
>+    // Check to see if the maintenance service is installed.
>+    // If it is don't show the preference at all.
>+    var installed = 0;
Should be able to just use var installed;

>+    try {
>+      Components.utils.reportError("0");
?

>+      var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
>+                .createInstance(Components.interfaces.nsIWindowsRegKey);
>+      wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
>+               "SOFTWARE\\Mozilla\\MaintenanceService",
>+               wrk.ACCESS_READ | wrk.WOW64_64);
>+      installed = wrk.readIntValue("Installed");


>diff --git a/toolkit/components/maintenanceservice/workmonitor.cpp b/toolkit/components/maintenanceservice/workmonitor.cpp
>--- a/toolkit/components/maintenanceservice/workmonitor.cpp
>+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
>@@ -56,76 +56,97 @@
> 
> extern BOOL gServiceStopping;
> 
> // Wait 45 minutes for an update operation to run at most.
> // Updates usually take less than a minute so this seems like a 
> // significantly large and safe amount of time to wait.
> static const int TIME_TO_WAIT_ON_UPDATER = 45 * 60 * 1000;
> PRUnichar* MakeCommandLine(int argc, PRUnichar **argv);
>-BOOL WriteStatusPendingNoService(LPCWSTR updateDirPath);
>+BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
>+BOOL WriteStatusPending(LPCWSTR updateDirPath);
> BOOL StartCallbackApp(int argcTmp, LPWSTR *argvTmp, DWORD callbackSessionID);
> BOOL StartSelfUpdate(int argcTmp, LPWSTR *argvTmp);
>+void LaunchWinPostProcess(const WCHAR *appExe, HANDLE userToken = NULL);
>+BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer,  LPCWSTR siblingFilePath, 
>+                            LPCWSTR newFileName);
>+
>+// Error codes is 16000 since Windows system error codes only go up to 15999.
nit: s/codes/code/

>+const int SERVICE_UPDATE_ERROR = 16000;

>...
>   // If we're about to start the update process from session 0 on Vista
>-  // or later, then we should not show a GUI.  One way to disable the
>-  // updater.exe GUI is to delete the updater.ini file.
>+  // or later, then we should not show a GUI.
>   if (UACHelper::IsVistaOrLater() && argcTmp >= 2 ) {
>+    // Setting the desktop to blank will ensure no GUI is displayed
>     si.lpDesktop = L"";
>-    WCHAR updaterINI[MAX_PATH + 1];
>-    wcscpy(updaterINI, argvTmp[1]);
>-    if (PathAppendSafe(updaterINI, L"updater.ini")) {
>-      DeleteFileW(updaterINI);
>-    }
>+    si.dwFlags |= STARTF_USESHOWWINDOW;
>+    si.wShowWindow = SW_HIDE;
mentioned in previous review
Comment on attachment 576402 [details] [diff] [review]
Incremental changes on the big base patch. v2.

>diff --git a/toolkit/xre/nsWindowsRestart.cpp b/toolkit/xre/nsWindowsRestart.cpp
>--- a/toolkit/xre/nsWindowsRestart.cpp
>+++ b/toolkit/xre/nsWindowsRestart.cpp
>...
>@@ -552,8 +602,114 @@ WinLaunchChild(const PRUnichar *exePath,
>     if (lpMsgBuf)
>       LocalFree(lpMsgBuf);
>   }
> 
>   free(cl);
> 
>   return ok;
> }
>+
>+void
>+LaunchWinPostProcess(const WCHAR *appExe, HANDLE userToken = NULL)
Please add a javadoc comment.

I'd prefer to keep code that isn't used by the application out of application code. Is there a different file this could be added to?

Same goes for the set status code.
Attachment #576402 - Flags: review?(robert.bugzilla) → review-
(In reply to Robert Strong [:rstrong] (do not email) from comment #360)
> No, it isn't. The steps are a tad convoluted and I tend to get them wrong a
> few times before I get them right so I'll just do this over the weekend
> unless Axel or someone provide the details. If no one does provide details
> I'll post the process I used in this bug after I get them right.

Here's the techie version of it:

Test with http://hg.mozilla.org/l10n-central/fr/.
pip install compare-locales
Add 
ac_add_options --with-l10n-base=full/path/to/parent/of/fr
to your mozconfig. 
reconfigure.
make package
cd browser/locales
make unpack
make merge-fr LOCALE_MERGEDIR=$PWD/merge
make installers-fr LOCALE_MERGEDIR=$PWD/merge

Install the french build and give it a quick test spin so that you know it's actually french. Given that the strings landed, your service installer should be in French now, too. (They're quick)
(In reply to Ehsan Akhgari [:ehsan] from comment #359)
> Created attachment 576663 [details] [diff] [review] [diff] [details] [review]
> WIP - Adding tests using the maintenance service

So, here's a problem.  I pushed a try job yesterday with this patch, and I expected the test to fail on Windows, but it passed.  Upon closer investigation, it should have been obvious to me that in case that the service cannot be started, we just fall back to not using the service and launching updater.exe directly.  While that's definitely what we want, it hurts our ability to test that the service was really used.  I've looked through the service code again, but there doesn't seem to be any traces left from the execution of the service (except for the log, which we can't use since we won't have admin privileges required to set system environment variables for NSPR logging.  :(

Should I add some sort of a trace (for example, writing a file somewhere, etc) and check for it in the test to make sure that it was really the service which initiated the update process?  If yes, what do you suggest that trace to be?

Thanks!
> expected the test to fail on Windows.
> Upon closer investigation, it should have been obvious 
> to me that in case that the service cannot be started, 
> we just fall back to not using the service and launching 
> updater.exe directly. 

That makes me feel better at least in general :)

> except for the log, which we can't use since we won't have admin 
> privileges required to set system environment variables for NSPR logging. 

I spoke to rs about this earlier (and as we discussed as well). I'll be removing NSPR logging in the next patch I submit (hopefully today). Instead I'll add normal logging.  I htink the best bet would be to add a function that reads in that log and searches for some finalized success code in the log after running ecah test.

> Should I add some sort of a trace (for example, writing a file 
> somewhere, etc) and check for it in the test to make sure that 
> it was really the service which initiated the update process? 

I think just using the log would be best and would automatically test that logging is working correctly.  And I'll handle that code to create the log.
In the test code you can just read in the entire log (should only be about 10 lines at any given time) and then search for the string "updater.exe was launched and run successfully" inside of it.  I'm open to suggestions on where to keep the log.  I was planning to put it to all users program data under Mozilla/logs, but let me know if you have a better suggestion.   I plan to keep at least the last 1 old log as well by the way, but perhaps up to the last 10 old logs.   We can make the newest log always have a consistent name though.
Thanks for the info Pike.
Using the log makes sense to me.  We put the current update.log file in the update root directory.  I don't have any strong preferences on where the log file from the service should live, as long as its location and name are consistent.
Just wanted to give an update:
- Pretty much all review comments are implemented modulo a couple questions I sent to rs via email.
- I'm making a shared library right now to be used between udpater and maintenanceservice to avoid having duplicated code (for logging), and so that code only used by updater and maintenanceservice doesn't need to be in toolkit/xre/
- Pending still, I need to implement callback app being called by updater.exe instead of maintenanceservice for various reasons that ehsan pointed out.
Attached patch Automated tests (v1) (obsolete) — Splinter Review
OK, this patch adds automated tests for all of the tests invoking the updater application using the service to run updater.exe.
Attachment #576663 - Attachment is obsolete: true
Attachment #576955 - Flags: review?(robert.bugzilla)
Attachment #576955 - Flags: review?(netzen)
No longer depends on: 702963
Review feedback and other things changed/fixed:

- Added code so Windows 2000 will not install the service nor show the option
- Since elevation type and IsUserAdmin were no longer used from UACHelper I removed it.   
  - It's very possible we'll use this for future maintenance service code, but if so I'll just regrab the code from an existing patch.
- Removed special handling from the installer for the all users application data work item folder, since there is an inherited write file attribute already set which is all that is needed.
  - Adding more permissions than we need can be problematic because limited user accounts can for example delete the folder.
- Added ifdef for Firefox so no other applications try to use the service that use the code.
- > +!define CompanyName "Mozilla Corporation" 
  > We need to think about how 3rd parties can use this.
  Do we need to worry about this initially, or is this just a comment to keep in mind for the future?
  If I can post a bug instead of handling now please let me know.
- > I suspect a is admin check is needed here
  Yup fixed.
- > I recall ERROR_SERVICE_MARKED_FOR_DELETE preventing the install of a service with 
  > the same name back in Win2K days. Have you / can you verify what happens in this case? 
  > Might be a good thing to verify on Win XP that the behavior is the same.
  The uninstall will work (eventually), if we are re-installing though that will fail. 
  I don't think there's anything we can do in that case though.  The user wouldn't have 
  a service temporarily.  I don't know of any case this will ever happen though since we always properly stop and delete.
- I lowercased the path before calculating the registry hash path to be safer.  Makes deployments for build servers easier.
- I now let updater.exe run its own callback application for various reasons that ehsan pointed out.  I still have to run it from the service in certain error conditions though.
- Added code inside updater.exe that checks for an env var set with the sessionID for the callback.  This can only be set on the system account env vars so is not a security risk.
- Added ifdef in toolkit/xre and in js so that service stuff isn't checked nor set initially for other products.
- CreateProcess had a static memory string used for cmdline which you shouldn't do that I meant to fix for a while, fixed now.
- Fixed bug with slogFile updater log path for refactored LaunchWinPostProcess function
- Remove structured exception handling code from CheckCertificateForPEFile since mingw doens't support it.
- Added logging to better identify if update files are left in directory (old bug)
- Removed icon and use uninstall icon instead. This saves 91KB on the installer and full MAR updates.
Attachment #574803 - Attachment is obsolete: true
Attachment #576402 - Attachment is obsolete: true
Attachment #577191 - Flags: review?(robert.bugzilla)
- Removed signer info checks since that can be easily spoofed by anyone, and leaves more room for error from RelEng side for when signing binaries.  Now check just the name and the issuer.
- Fixed race condition when starting PostUpdate under session 0 and the callback app.  If both are async sometimes the HKCU one could be set first leading to 2 uninstall icons.
Attachment #577191 - Attachment is obsolete: true
Attachment #577302 - Flags: review?(robert.bugzilla)
Attachment #577191 - Flags: review?(robert.bugzilla)
Attachment #574298 - Flags: review?(bsmith)
Attachment #574298 - Attachment is obsolete: true
Comment on attachment 577302 [details] [diff] [review]
All maintenance service and related code. v4.

I obsoleted old patch for security review from bsmith since this one is more up to date.

In case you can't review the entire patch, I think the most important files for security review are:
maintenanceservice_installer.nsi | registrycertificates.cpp | registrycertificates.h | uachelper.cpp | uachelper.h | workmonitor.cpp | workmonitor.h
Attachment #577302 - Flags: review?(bsmith)
2 minor changes:
- Fixed Uninstall icon, it was a generic file icon since the maintenanceservice.exe icon was removed. Now it the Uninstall.exe icon.
- Fixed build problem with updatedefines.h and a different one on non-windows platforms.

If you already started reviewing the old patch that is fine, as the changes are very minor.
Attachment #577302 - Attachment is obsolete: true
Attachment #577670 - Flags: review?(robert.bugzilla)
Attachment #577670 - Flags: review?(bsmith)
Attachment #577302 - Flags: review?(robert.bugzilla)
Attachment #577302 - Flags: review?(bsmith)
Comment on attachment 577670 [details] [diff] [review]
All maintenance service and related code. v5.

Review of attachment 577670 [details] [diff] [review]:
-----------------------------------------------------------------

a general comment: we should use all the available OS protections for the new binaries (ASLR, DEP, etc.). The new executables
should pass a run of Microsoft's SDL binscope tool as Firefox itself does. 

overall looks good, with some nits and there's a couple of questions i would like clarification on around the post process
and the callback.

::: toolkit/components/maintenanceservice/certificatecheck.cpp
@@ +329,5 @@
> +    case TRUST_E_NOSIGNATURE:
> +      // The file was not signed or had a signature that was not valid.
> +      // Get the reason for no signature.
> +      if (TRUST_E_TIME_STAMP == dwLastError) {
> +        // The file was not signed.

this comment looks incorrect ?

::: toolkit/components/maintenanceservice/maintenanceservice.cpp
@@ +182,5 @@
> +  wcscpy(path, basePath);
> +  if (logNumber <= 0) {
> +    swprintf(logName, L"maintenanceservice.log");
> +  } else {
> +    swprintf(logName, L"maintenanceservice-%d.log", logNumber);

this is pretty picky, but %d can be 10 chars long (the check <= 0 means it cant have a - in front of it making it 11)
the rest of the hardcoded chars are 23 chars long, could be a possible off by one in the
pathological case - this can't happen though since we only keep 5 logs based on the #define above

::: toolkit/components/maintenanceservice/pathhash.cpp
@@ +141,5 @@
> +    filePathLen--;
> +  }
> +
> +  WCHAR *lowercasePath = new WCHAR[filePathLen + 1];
> +  wcscpy(lowercasePath, filePath);

doesn't this copy the slash we wanted to ignore above ?

::: toolkit/components/maintenanceservice/workmonitor.cpp
@@ +211,5 @@
> +      // to figure out where.
> +      // The directory containing the update information.
> +      LaunchWinPostProcess(callbackApplication, updateInfoDir, NULL);
> +      nsAutoHandle userToken(UACHelper::OpenUserToken(callbackSessionID));
> +      LaunchWinPostProcess(callbackApplication, updateInfoDir, userToken);

i'm a little confused here. it seems like the service will now always execute the callback app (instead of updater.exe launching the callback app)

why do we launch the post process twice, once elevated and once as the user ? i thought the post update process and the callback were the same thing ? this is probably
my misunderstanding. 

is there a case where updater.exe will launch the callback app ever ?

@@ +343,5 @@
> +  int argcTmp = 0;
> +  LPWSTR* argvTmp = CommandLineToArgvW(cmdlineBufferWide, &argcTmp);
> +
> +  // Check for callback application sign problems
> +  BOOL callbakSignProblem = FALSE;

nit: typo

@@ +414,5 @@
> +         sessionID, GetLastError()));
> +
> +    // When there is a certificate error we just want to write pending.
> +    // That is because a future update will probably fix the certificate
> +    // problem, and we don't want to update  app.update.service.failcount.

this won't be true for a malicious work item pointing to a bogus callback.. but we're
not planning to enable signed callbacks any time soon right ?

@@ +435,5 @@
> +    }
> +
> +    // On certificate check errors on updater.exe, updater.exe won't run at all
> +    // so we need to manually start the callback application ourselves.
> +    // This condition will only be hit when the callback app has no sign errors

except we don't check the callback app and don't plan to - maybe we should
consider not running the callback in this case ? 

i still have concerns about injecting a callback into another user's sessions and being able to specify
args, this must be researched during security testing of the feature IMO

@@ +548,5 @@
> +  wcscpy(maintserviceInstallerPath, argvTmp[2]);
> +  PathAppendSafe(maintserviceInstallerPath, 
> +                 L"maintenanceservice_installer.exe");
> +  WCHAR cmdLine[64];
> +  wcscpy(cmdLine, L"app.exe /Upgrade");

what's the hardcoded app.exe here ?

::: toolkit/mozapps/update/common/updatedefines.h
@@ +70,5 @@
> +# define snprintf(dest, count, fmt, ...) \
> +  PR_BEGIN_MACRO \
> +  int _count = count - 1; \
> +  _snprintf(dest, _count, fmt, ##__VA_ARGS__); \
> +  dest[_count] = '\0'; \

if dest is WCHAR this should be L'\0' to terminate with a 2 byte NULL i think ? 

making this a macro to ensure null termination is awesome, thank you.

::: toolkit/mozapps/update/nsUpdateService.js
@@ +611,5 @@
> +                          PREF_APP_UPDATE_SERVICE_FAILCOUNT, 0);
> +  var maxFail = getPref("getIntPref", 
> +                        PREF_APP_UPDATE_SERVICE_MAXFAIL, 
> +                        DEFAULT_MAX_FAIL_COUNT);
> +  return useService && failCount < maxFail;

do we ever reset the failCount ? or if it fails 10 times, we just never try the service ever again ?
Attachment #577670 - Flags: feedback+
Thanks for the review Ian!
I implemented the comments and attached a new patch.
If you want to see only the changes you can see them here: 
http://hg.mozilla.org/projects/elm/rev/8f67123eaebe

> +  wcscpy(lowercasePath, filePath);
> doesn't this copy the slash we wanted to ignore above ?

It does but that's OK because we calculate the hash based only on filePathLen.
I updated with a comment to clarify this.



> i'm a little confused here. it seems like the service will now always execute the callback app (instead of updater.exe 
> launching the callback app)
> ...
> is there a case where updater.exe will launch the callback app ever ?

updater.exe will always execute the callback app itself because of various dependencies on these parameters that updater.exe needs.
The maintenance service will execute the callback app only if updater.exe couldn't be started and the callback app's signature is verified.
 


> why do we launch the post process twice, once elevated and once as the user ? 
> i thought the post update process and the callback were the same thing ? 
> this is probably my misunderstanding. 

We do this because some fields need to be set in HKLM so we run it as session 0.
Then we need to set some per user stuff so we launch it with the user token (unelevated)


> this won't be true for a malicious work item pointing to a bogus callback.. but we're
> not planning to enable signed callbacks any time soon right ?
> ...
> except we don't check the callback app and don't plan to - mayrsbe we should
> consider not running the callback in this case ? >
> ...
> i still have concerns about injecting a callback into another user's sessions and being able to specify
> args, this must be researched during security testing of the feature IMO

I'm not sure if RelEng will be done the automated signing by the time we would like to land this on Nightly or not.
It is my understanding that they are close.
But the plan is definitely to check the signature on the callback before executing it. 
So you can only execute a callback that is signed by us.
Please advise on if the callback sign check can be disabled for Nightly or not. 
I think previously the understanding was that it was OK on Nightly but not Aurora, but I could be wrong so please confirm.

If you want before we land we can disable the service starting the callback app if RelEng isn't ready.  
But I think it's just as much of a problem for updater.exe to execute the callback app.


> wcscpy(cmdLine, L"app.exe /Upgrade");
> what's the hardcoded app.exe here ?

Just a dummy parameter that is unused, I renamed it to dummyparam.exe



> if dest is WCHAR this should be L'\0' to terminate with a 2 byte NULL i think ? 
> making this a macro to ensure null termination is awesome, thank you.

That is just code moved from updater so I can't take credit.
I changed L'\0' as I think that's correct.



> do we ever reset the failCount ? or if it fails 10 times, 
> we just never try the service ever again ?

Ya we never reset it currently and if it reaches that it will never be used again.  
I think we may eventually reset it via add on update or disable it completely.
rs in Comment 325 asked that it be added, I think as a safety measure.
Attachment #577670 - Attachment is obsolete: true
Attachment #577824 - Flags: review?(robert.bugzilla)
Attachment #577670 - Flags: review?(robert.bugzilla)
Attachment #577670 - Flags: review?(bsmith)
Attached patch Automated tests (v2) (obsolete) — Splinter Review
This is a better version of the automated tests which uses the maintenanceservice.log file to verify that the service was indeed used in order to perform the update in the new tests.
Attachment #576955 - Attachment is obsolete: true
Attachment #577952 - Flags: review?(robert.bugzilla)
Attachment #577952 - Flags: review?(netzen)
Attachment #576955 - Flags: review?(robert.bugzilla)
Attachment #576955 - Flags: review?(netzen)
Depends on: 706573
Couple of notes

The pref associated with using the service should be set to disable the service when the failCount reaches the threshold and failCount should reset after the pref associated with using the service is set to disable using the service.

I haven't been able to compile with pymake with this patch applied. I don't know if it is a pymake problem or due to the patch so I am building using pymake without the patch. Have you been using pymake?

I tried to repackage without success. I will also test repackaging without the patch at the same time I check if pymake is working.
> The pref associated with using the service should be set to disable 
> the service when the failCount reaches the threshold 
> and failCount should reset after the pref associated with using the 
> service is set to disable using the service.

That sounds better, then someone can just turn it on via manually going to preferences again.  Thanks.  I'll make that change on the next patch.

> I haven't been able to compile with pymake with this patch applied.

I've only tried building with make -f client.mk (and incremental builds).  Also it passes try tests. 
I haven't tested the localization repackaging yet given in comment 372.
I have used the following to repackage on Windows using an l10n dir alongside my source dir without compare-locales

Create an l10n dir alongside the source dir

For the French locale
cd into the l10n dir
hg clone http://hg.mozilla.org/l10n-central/fr/ fr

In my mozconfig:
mk_add_options MOZ_LOCALE_DIRS=@TOPSRCDIR@/../l10n
ac_add_options --with-l10n-base=../l10n

reconfigure

make package

cd browser/locales
make installers-fr
(In reply to Brian R. Bondy [:bbondy] from comment #387)
> > The pref associated with using the service should be set to disable 
> > the service when the failCount reaches the threshold 
> > and failCount should reset after the pref associated with using the 
> > service is set to disable using the service.
> 
> That sounds better, then someone can just turn it on via manually going to
> preferences again.  Thanks.  I'll make that change on the next patch.
Also reset it if the service successfully updates and it has a value
btw: iirc two or three versions previous to the current patch did compile with pymake... still compiling and will report back whether the current patch breaks pymake
(In reply to Robert Strong [:rstrong] (do not email) from comment #388)
> I have used the following to repackage on Windows using an l10n dir
> alongside my source dir without compare-locales
> 
> Create an l10n dir alongside the source dir
> 
> For the French locale
> cd into the l10n dir
> hg clone http://hg.mozilla.org/l10n-central/fr/ fr
> 
> In my mozconfig:
> mk_add_options MOZ_LOCALE_DIRS=@TOPSRCDIR@/../l10n
> ac_add_options --with-l10n-base=../l10n
> 
> reconfigure
> 
> make package
> 
> cd browser/locales
> make installers-fr

I'd use a full path to l10n base, and I miss the merge step here, and passing the full path to LOCALE_MERGEDIR in both merge-fr and installers-fr. Beyond that, if you can pastebin the exact error, I should be able to help.
Using the steps from comment #388 (en-GB with missing files added) without the patch repackaging works.

Using the steps from comment #388 (en-GB with missing files added) with the patch I get the following:
updating: chrome.manifest (172 bytes security) (deflated 76%)
make[1]: Entering directory `/c/moz/_1_mozilla-central/ff-x86-opt/browser/locale
s'
make[1]: *** No rule to make target `/c/moz/_1_mozilla-central/ff-x86-opt/browse
r/locales/../../dist/install/sea/firefox-11.0a1.en-US.win32.installer.exe', need
ed by `repackage-win32-installer'.  Stop.
make[1]: Leaving directory `/c/moz/_1_mozilla-central/ff-x86-opt/browser/locales
'
make: *** [repackage-win32-installer-en-GB] Error 2
Attached patch Automated tests (v3) (obsolete) — Splinter Review
Added a bootstrapping test for getting the built version of the service in place, and also made a few robustness improvements to the test.
Attachment #577952 - Attachment is obsolete: true
Attachment #578107 - Flags: review?(robert.bugzilla)
Attachment #578107 - Flags: review?(netzen)
Attachment #577952 - Flags: review?(robert.bugzilla)
Attachment #577952 - Flags: review?(netzen)
building with pymake

processing ../../../../mozilla/toolkit/mozapps/extensions/jar.mn
make.py[4]: Leaving directory 'c:\moz\_1_mozilla-central\ff-x86-opt\toolkit\moz
pps/extensions'
c:\moz\_1_mozilla-central\mozilla\config\makefiles\target_libs.mk:54:0: command
'c:/moz/mozilla-build/python/python.exe c:/moz/_1_mozilla-central/mozilla/build
pymake/pymake/../make.py -C toolkit libs' failed, return code 2
c:\moz\_1_mozilla-central\mozilla\config\rules.mk:744:0: command 'c:/moz/mozill
-build/python/python.exe c:/moz/_1_mozilla-central/mozilla/build/pymake/pymake/
./make.py libs_tier_platform' failed, return code 2
c:\moz\_1_mozilla-central\mozilla\config\rules.mk:709:0: command 'c:/moz/mozill
-build/python/python.exe c:/moz/_1_mozilla-central/mozilla/build/pymake/pymake/
./make.py  tier_platform' failed, return code 2
(In reply to Brian R. Bondy [:bbondy] from comment #384)

> updater.exe will always execute the callback app itself because of various
> dependencies on these parameters that updater.exe needs.
> The maintenance service will execute the callback app only if updater.exe
> couldn't be started and the callback app's signature is verified.

ok, thanks for the clarification.

> We do this because some fields need to be set in HKLM so we run it as
> session 0.
> Then we need to set some per user stuff so we launch it with the user token
> (unelevated)

are these the same executable ? i assume it just looks at its session id to work out which things to do if so. is this different at all from the update process prior to the service work ? 
 
> I'm not sure if RelEng will be done the automated signing by the time we
> would like to land this on Nightly or not.
> It is my understanding that they are close.
> But the plan is definitely to check the signature on the callback before
> executing it. 
> So you can only execute a callback that is signed by us.
> Please advise on if the callback sign check can be disabled for Nightly or
> not. 
> I think previously the understanding was that it was OK on Nightly but not
> Aurora, but I could be wrong so please confirm.

that's my understanding of the discussion previously too - nightly is ok, other channels are not

> If you want before we land we can disable the service starting the callback
> app if RelEng isn't ready.  
> But I think it's just as much of a problem for updater.exe to execute the
> callback app.

yeah, they're both elevated so it seems equivalent
Depends on: 706716
> are these the same executable ? i assume it just looks at 
> its session id to work out which things to do if so. 
> is this different at all from the update process prior to the service work ? 

The code branches off on if you have access or not. The HKCU is executed secondly and has special handling to not add an uninstall icon if it is already added.  rs and I discussed running 2 post processes previously on a phone call.  I'm not sure if that'll cause problems with other apps or not, so we might be changing this eventually.  For now the service is only ifdef'ed to work with Firefox though.  I'd like to eventually have 2 different callback parameters for people who want to use the service but that'll probaly be in another bug ID.
(In reply to Robert Strong [:rstrong] (do not email) from comment #392)
> Using the steps from comment #388 (en-GB with missing files added) without
> the patch repackaging works.
> 
> Using the steps from comment #388 (en-GB with missing files added) with the
> patch I get the following:
> updating: chrome.manifest (172 bytes security) (deflated 76%)
> make[1]: Entering directory
> `/c/moz/_1_mozilla-central/ff-x86-opt/browser/locale
> s'
> make[1]: *** No rule to make target
> `/c/moz/_1_mozilla-central/ff-x86-opt/browse
> r/locales/../../dist/install/sea/firefox-11.0a1.en-US.win32.installer.exe',
> need
> ed by `repackage-win32-installer'.  Stop.
> make[1]: Leaving directory
> `/c/moz/_1_mozilla-central/ff-x86-opt/browser/locales
> '
> make: *** [repackage-win32-installer-en-GB] Error 2
I repackaged using the fixed pymake build and it worked. Not sure what caused the initial failures.
>diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js
>--- a/browser/base/content/aboutDialog.js
>+++ b/browser/base/content/aboutDialog.js
>@@ -180,18 +180,22 @@ function appUpdater()
> appUpdater.prototype =
> {
>   // true when there is an update check in progress.
>   isChecking: false,
> 
>   // true when there is an update already staged / ready to be applied.
>   get isPending() {
>     if (this.update)
>-      return this.update.state == "pending";
>-    return this.um.activeUpdate && this.um.activeUpdate.state == "pending";
>+      return this.update.state == "pending" || 
>+             this.update.state == "pending-service";
nit: add braces since it is on two lines now.

>diff --git a/browser/components/preferences/advanced.js b/browser/components/preferences/advanced.js
>--- a/browser/components/preferences/advanced.js
>+++ b/browser/components/preferences/advanced.js
>@@ -507,16 +507,34 @@ var gAdvancedPane = {
>     // A locked pref is sufficient to disable the radiogroup.
>     radiogroup.disabled = !canCheck || enabledPref.locked || autoPref.locked;
> 
>     var modePref = document.getElementById("app.update.mode");
>     var warnIncompatible = document.getElementById("warnIncompatible");
>     // the warnIncompatible checkbox value is set by readAddonWarn
>     warnIncompatible.disabled = radiogroup.disabled || modePref.locked ||
>                                 !enabledPref.value || !autoPref.value;
>+
>+    // Check to see if the maintenance service is installed.
>+    // If it is don't show the preference at all.
>+    var installed;
>+    try {
>+      Components.utils.reportError("0");
>+      var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
>+                .createInstance(Components.interfaces.nsIWindowsRegKey);
>+      wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
>+               "SOFTWARE\\Mozilla\\MaintenanceService",
>+               wrk.ACCESS_READ | wrk.WOW64_64);
>+      installed = wrk.readIntValue("Installed");
>+      wrk.close();
>+    } catch(e) {
>+    }
>+    if (!installed) {
So, any value that evaluates to not true will hide the control. Might be better to check for the explicit value.

>+      document.getElementById("useService").hidden = true;
>+    }
>   },
>diff --git a/toolkit/Makefile.in b/toolkit/Makefile.in
>--- a/toolkit/Makefile.in
>+++ b/toolkit/Makefile.in
>@@ -41,27 +41,27 @@
> DEPTH     = ..
> topsrcdir = @top_srcdir@
> srcdir    = @srcdir@
> VPATH     = @srcdir@
> 
> include $(DEPTH)/config/autoconf.mk
> 
> PARALLEL_DIRS = \
>-  components \
>   content \
>   locales \
>   mozapps/downloads \
>   mozapps/extensions \
>   mozapps/handling \
>   mozapps/preferences \
>   mozapps/plugins \
>   mozapps/shared \
>   mozapps/update \
>   mozapps/webapps \
>+  components \
If this change isn't necessary just leave it like it was

>   obsolete \
>   profile \
>   themes \
>   $(NULL)
> 
> ifneq (,$(filter gtk2 qt,$(MOZ_WIDGET_TOOLKIT)))
> PARALLEL_DIRS += system/unixproxy
> endif
>diff --git a/toolkit/components/Makefile.in b/toolkit/components/Makefile.in
>--- a/toolkit/components/Makefile.in
>+++ b/toolkit/components/Makefile.in
>@@ -72,16 +72,20 @@ PARALLEL_DIRS += \
>   statusfilter \
>   telemetry \
>   typeaheadfind \
>   urlformatter \
>   viewconfig \
>   viewsource \
>   $(NULL)
> 
>+ifeq ($(OS_ARCH),WINNT)
>+PARALLEL_DIRS += maintenanceservice
>+endif
This being parallel along with toolkit/mozapps/update/common appears to be what is breaking pymake.
>diff --git a/other-licenses/nsis/Contrib/ServicesHelper/ServicesHelper.rc b/other-licenses/nsis/Contrib/ServicesHelper/ServicesHelper.rc
>new file mode 100644
>--- /dev/null
>+++ b/other-licenses/nsis/Contrib/ServicesHelper/ServicesHelper.rc
>@@ -0,0 +1,99 @@
>...
>+BEGIN
>+    BLOCK "StringFileInfo"
>+    BEGIN
>+        BLOCK "040904b0"
>+        BEGIN
>+            VALUE "FileDescription", "NSIS Plug-in for managing Windows services"
>+            VALUE "FileVersion", "1, 0, 0, 0"
>+            VALUE "InternalName", "ServicesHelper"
>+            VALUE "LegalCopyright", "Original code Copyright (c) 2011 Mozilla Corporation"
I think we want "License: MPL 1.1/GPL 2.0/LGPL 2.1"

Please double check with Gerv.
Please see Comment 399 regarding the file info properties in the .rc file.
Attachment #578218 - Flags: review?(gerv)
Implemented review comments from rs.
Also built successfully with pymake.

> include $(DEPTH)/config/autoconf.mk
> ... move of components ...
> If this change isn't necessary just leave it like it was

I was getting a build error with make -f client.mk without changing this.
Attachment #577824 - Attachment is obsolete: true
Attachment #578225 - Flags: review?(robert.bugzilla)
Attachment #578225 - Flags: review?(bsmith)
Attachment #577824 - Flags: review?(robert.bugzilla)
Comment on attachment 578222 [details] [diff] [review]
Alternate file info / licensing info rc file as per :rs' suggestion

Go with this one, but with + signs:

VALUE "LegalCopyright", "MPL 1.1+/GPL 2.0+/LGPL 2.1+"

Gerv
Attachment #578222 - Flags: review?(gerv) → review+
Attachment #578222 - Attachment is obsolete: true
Attachment #578218 - Attachment is obsolete: true
Attachment #578218 - Flags: review?(gerv)
Updated license info on ServicesHelper.rc as per gerv's comment.
Attachment #578225 - Attachment is obsolete: true
Attachment #578230 - Flags: review?(robert.bugzilla)
Attachment #578230 - Flags: review?(bsmith)
Attachment #578225 - Flags: review?(robert.bugzilla)
Attachment #578225 - Flags: review?(bsmith)
In addition to the existing tests that ehsan ported over, I'd like to also test as many of these as is possible:

- Test that the service can be started from the current unelevated user
- Test that the service can be stopped from the current unelevated user
- Test that installed reg value is present @ HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService and has value of 1
- Test that attempted reg value is present @ HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService and has value of 1
- Test an invalid mar file
- Mar file is missing
- Invalid work item file
- Work item file locked
- Work item file doens't specify enough command line args (< 2)
- Should we be checking specific error codes?
- Cert check failure on updater
- Cert check failure on callback
- Cert check failure on helper.exe
- Cert check failure specifically that timestamp is expired
- Cert check failure specifically that it is not trusted
- Cert check failure specifically that it is trusted but the name and issuer do not match
- Mar sign checks:
 - Mar sign verify failure 
 - Signed mar verify success
- Disabled pref when update is available should write out pending not pending-svc
- Test max fail count should auto-set disabled pref and reset max fail count
- updater.exe can't be started, callback should be started
- Maybe make callback app with dummy args return a failurecode if run in session 0 on Vista or higher.
- Ensure updater is run as session 0 always if run through the service.
- The following tests maybe have a special command line arg to emulate the action but not actually do since they require admin?  Then it would write log?
 - Test that service gets updated to a newer version if you call maintenanceservice_tmp.exe update where maintenanceservice_tmp.exe is newer
 - Test that service gets updated to a newer version if you call maintenanceservice_tmp.exe install where maintenanceservice_tmp.exe is newer
 - Test that service does not get updated to an older version if you call maintenanceservice_tmp.exe update where maintenanceservice_tmp.exe is older
 - Test that service does not get updated to an older version if you call maintenanceservice_tmp.exe install where maintenanceservice_tmp.exe is older
 - Test that service gets removed if you call maintenanceservice_tmp.exe uninstall
 - Test that service gets installed if it doesn't exist if you call maintenanceservice_tmp.exe install
 - Test that service does not get installed if it doesn't exist and you call maintenanceservice_tmp.exe install
- Test to make sure uninstall screen has only 1 uninstall icon and not 2 (should exist only in HKCU or HKLM but not both), I don't think we can test this because we don't actually use the installer in tests right?
- Verify exactly 5 backup logs are kept if you do 6 updates.
Comment on attachment 578107 [details] [diff] [review]
Automated tests (v3)

Review of attachment 578107 [details] [diff] [review]:
-----------------------------------------------------------------

Looks good so far. 

I think there was a copy error here:
> copy from toolkit/mozapps/update/test/unit/test_0180_fileInUse_xp_win_complete.js
> copy to toolkit/mozapps/update/test/unit/test_0160_appInUse_xp_win_complete_svc.js

::: toolkit/mozapps/update/test/unit/head_update.js.in
@@ +489,5 @@
> +
> +    do_register_cleanup(function serviceCleanup() {
> +      resetEnvironment();
> +
> +      if (IS_WIN) {

Remove if (IS_WIN) { but keep contained code, unindent. Also throughout the rest of the files with _svc suffix.

@@ +512,5 @@
> +        if (IS_MACOSX) {
> +          // This will delete the version script and version file if they exist.
> +          getVersionScriptAndFile();
> +        }
> +      }

Remove the IS_UNIX block completely here.  Also throughout the rest of the file with _svc suffix.

::: toolkit/xre/nsAppRunner.cpp
@@ +3190,5 @@
> +      // to make sure that the maintenance service successfully launches the
> +      // callback application.
> +      const char *logFile = nsnull;
> +      if (ARG_FOUND == CheckArg("dump-args", false, &logFile)) {
> +        FILE* logFP = fopen(logFile, "wb");

Should check for NULL

::: toolkit/xre/nsUpdateDriver.cpp
@@ +568,5 @@
> +      NS_RELEASE(appDir);
> +      NS_ADDREF(appDir = overrideDir);
> +    }
> +  }
> +

Can these env variables be used to exploit anything with the service? Maybe security should approve them.
For example if we have a file that gets replaced that has the same name of a different file from a different directory someone wants to replace.
Attachment #578107 - Flags: review?(netzen)
Comment on attachment 572647 [details] [diff] [review]
Patch 7 - RAII base helpers. v3.

I really didn't need to rereview this ;-)
Attachment #572647 - Flags: superreview?(benjamin) → superreview+
(In reply to Brian R. Bondy [:bbondy] from comment #405)
> In addition to the existing tests that ehsan ported over, I'd like to also
> test as many of these as is possible:
> 
> - Test that the service can be started from the current unelevated user

I believe that this is already covered by my tests.

> - Test that the service can be stopped from the current unelevated user

We should be able to cover this by making sure that the service writes something to the log when it's finished and then check for that in the checkServiceLog function in my tests patch.

> - Test that installed reg value is present @
> HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService and has value of 1
> - Test that attempted reg value is present @
> HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService and has value of 1

Good idea.  We can read registry keys from the tests using the nsIWindowsRegKey APIs.

> - Test an invalid mar file

I don't think that the existing tests cover this.  Does anything bad happens if we encounter an invalid mar file?  Otherwise maybe we can file a followup bug about this.

> - Mar file is missing

Again, I believe that this might not be necessary in this bug if there isn't anything risky happening when the mar file is missing in presence of the service.

> - Invalid work item file

We could test this, it sounds like a good idea.  What types of invalid work item files are you thinking about?

> - Work item file locked

We have the capability of locking a file from the tests, so we can test this as well.

> - Work item file doens't specify enough command line args (< 2)

Same here.

> - Should we be checking specific error codes?

Like what?

> - Cert check failure on updater
> - Cert check failure on callback
> - Cert check failure on helper.exe
> - Cert check failure specifically that timestamp is expired
> - Cert check failure specifically that it is not trusted
> - Cert check failure specifically that it is trusted but the name and issuer
> do not match

These are all good ideas.  But I don't know how we would create binaries with invalid signatures.  If all you want is an unsigned binary, then we could probably use the TestAUSHelper app in place of each of these executables.  But that will not cover the last three cases.

> - Mar sign checks:
>  - Mar sign verify failure 
>  - Signed mar verify success

These are all good ideas, but I don't know a lot about mar file signing, so I don't have any ideas here.  :-)

> - Disabled pref when update is available should write out pending not
> pending-svc

We should also check to make sure that with the pref enabled it writes pending-service, cause all of my tests just generate a synthesized update.  :-)

> - Test max fail count should auto-set disabled pref and reset max fail count
> - updater.exe can't be started, callback should be started

All good ideas.

> - Maybe make callback app with dummy args return a failurecode if run in
> session 0 on Vista or higher.

Hmm, the callback app in my tests is firefox.exe itself.  Maybe we should build this check into Firefox itself, and ship it with that check?  I can't imagine why anyone would ever want to run Firefox under session 0.  ;-)

> - Ensure updater is run as session 0 always if run through the service.

Likewise, maybe we should build this check into the updater itself?

> - The following tests maybe have a special command line arg to emulate the
> action but not actually do since they require admin?  Then it would write
> log?
>  - Test that service gets updated to a newer version if you call
> maintenanceservice_tmp.exe update where maintenanceservice_tmp.exe is newer
>  - Test that service gets updated to a newer version if you call
> maintenanceservice_tmp.exe install where maintenanceservice_tmp.exe is newer
>  - Test that service does not get updated to an older version if you call
> maintenanceservice_tmp.exe update where maintenanceservice_tmp.exe is older
>  - Test that service does not get updated to an older version if you call
> maintenanceservice_tmp.exe install where maintenanceservice_tmp.exe is older
>  - Test that service gets removed if you call maintenanceservice_tmp.exe
> uninstall
>  - Test that service gets installed if it doesn't exist if you call
> maintenanceservice_tmp.exe install
>  - Test that service does not get installed if it doesn't exist and you call
> maintenanceservice_tmp.exe install

All of these are good ideas, but they require us to build a version of the maintenance service binary with a different version.  That might be sort of a hassle (although maybe we can just have alternate resource scripts with hardcoded version numbers which are guaranteed to always be smaller or larger than the real version?  (I don't know how fragile this would be.)

> - Test to make sure uninstall screen has only 1 uninstall icon and not 2
> (should exist only in HKCU or HKLM but not both), I don't think we can test
> this because we don't actually use the installer in tests right?

Are you talking about this NSIS installer?  I'm afraid that we currently don't have any means of testing the UI of NSIS installers, as they're native (non-XUL) apps.  AFAIK, we currently don't have any sort of testing for our installer.  :/

> - Verify exactly 5 backup logs are kept if you do 6 updates.

We could easily do this by adding a "9999" test which runs at the end and counts the number of log files, cause the existing tests should run the updater more than 6 updates.
(In reply to Brian R. Bondy [:bbondy] from comment #406)
> Comment on attachment 578107 [details] [diff] [review] [diff] [details] [review]
> Automated tests (v3)
> 
> Review of attachment 578107 [details] [diff] [review] [diff] [details] [review]:
> -----------------------------------------------------------------
> 
> Looks good so far. 
> 
> I think there was a copy error here:
> > copy from toolkit/mozapps/update/test/unit/test_0180_fileInUse_xp_win_complete.js
> > copy to toolkit/mozapps/update/test/unit/test_0160_appInUse_xp_win_complete_svc.js

Ah, this is just git being stupid.  :(  I needed to change this test because it turns out that the test succeeds using the Windows service.  In reality, this is a copy of the toolkit/mozapps/update/test/unit/test_0160_appInUse_xp_win_complete.js test file.

> ::: toolkit/mozapps/update/test/unit/head_update.js.in
> @@ +489,5 @@
> > +
> > +    do_register_cleanup(function serviceCleanup() {
> > +      resetEnvironment();
> > +
> > +      if (IS_WIN) {
> 
> Remove if (IS_WIN) { but keep contained code, unindent. Also throughout the
> rest of the files with _svc suffix.

Will do.  I tried to keep the changes to the tests minimal (some of the original tests run on all platforms), hence the IS_WIN checks.

> @@ +512,5 @@
> > +        if (IS_MACOSX) {
> > +          // This will delete the version script and version file if they exist.
> > +          getVersionScriptAndFile();
> > +        }
> > +      }
> 
> Remove the IS_UNIX block completely here.  Also throughout the rest of the
> file with _svc suffix.

Same here.  :-)  Will fix in the next version of the patch.

> ::: toolkit/xre/nsAppRunner.cpp
> @@ +3190,5 @@
> > +      // to make sure that the maintenance service successfully launches the
> > +      // callback application.
> > +      const char *logFile = nsnull;
> > +      if (ARG_FOUND == CheckArg("dump-args", false, &logFile)) {
> > +        FILE* logFP = fopen(logFile, "wb");
> 
> Should check for NULL

Oops, you're right!

> ::: toolkit/xre/nsUpdateDriver.cpp
> @@ +568,5 @@
> > +      NS_RELEASE(appDir);
> > +      NS_ADDREF(appDir = overrideDir);
> > +    }
> > +  }
> > +
> 
> Can these env variables be used to exploit anything with the service? Maybe
> security should approve them.
> For example if we have a file that gets replaced that has the same name of a
> different file from a different directory someone wants to replace.

I will ping Ian about this, but I think this should not be an issue, since if you have access to set user env variables, then there's also other stuff that they can do...
Attached patch Automated tests (v4) (obsolete) — Splinter Review
Addressed Brian's comments.
Attachment #578107 - Attachment is obsolete: true
Attachment #578284 - Flags: review?(robert.bugzilla)
Attachment #578107 - Flags: review?(robert.bugzilla)
(In reply to Ehsan Akhgari [:ehsan] from comment #409)
> > Can these env variables be used to exploit anything with the service? Maybe
> > security should approve them.
> > For example if we have a file that gets replaced that has the same name of a
> > different file from a different directory someone wants to replace.
> 
> I will ping Ian about this, but I think this should not be an issue, since
> if you have access to set user env variables, then there's also other stuff
> that they can do...

yeah, i think Ehsan is right here - these are user env variables, and other users shouldn't be able to set them, except for SYSTEM, and if you have that... :)
Depends on: 707020
Blocks: 707037
(In reply to Brian R. Bondy [:bbondy] from comment #405)
> In addition to the existing tests that ehsan ported over, I'd like to also
> test as many of these as is possible:
> 
> - Test that the service can be started from the current unelevated user
> - Test that the service can be stopped from the current unelevated user
Yes - should be simple

> - Test that installed reg value is present @
> HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService and has value of 1
Yes - should be simple

> - Test that attempted reg value is present @
> HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService and has value of 1
Yes - should be simple

> - Test an invalid mar file
If it isn't too difficult to get done in time for landing

> - Mar file is missing
If it isn't too difficult to get done in time for landing

> - Invalid work item file
If it isn't too difficult to get done in time for landing

> - Work item file locked
There is already a test binary for locking files for app update tests so if it isn't too difficult for landing

> - Work item file doens't specify enough command line args (< 2)
If it isn't too difficult to get done in time for landing

> - Should we be checking specific error codes?
All possible error codes should be tested if it is possible to test them. For example, the pre-existing elevation cancel error is too difficult to test as I see it.

> - Cert check failure on updater
> - Cert check failure on callback
> - Cert check failure on helper.exe
> - Cert check failure specifically that timestamp is expired
> - Cert check failure specifically that it is not trusted
> - Cert check failure specifically that it is trusted but the name and issuer
> do not match
If possible on landing day, if not soon afterward

> - Mar sign checks:
>  - Mar sign verify failure 
>  - Signed mar verify success
Would very much like to see this tested when it lands

> - Disabled pref when update is available should write out pending not
> pending-svc
Yes - should be fairly simple

> - Test max fail count should auto-set disabled pref and reset max fail count
Yes - should be simple

> - updater.exe can't be started, callback should be started
Since there are tests to verify the updater can be launched I assume this is when something extremely unusual has occured. I think this can wait until after landing 

> - Maybe make callback app with dummy args return a failurecode if run in
> session 0 on Vista or higher.
I think this can wait until after landing

> - Ensure updater is run as session 0 always if run through the service.
If possible on landing day, if not soon afterward if you think it is needed

> - The following tests maybe have a special command line arg to emulate the
> action but not actually do since they require admin?  Then it would write
> log?
I think it would be more valuable to spend time to make the already installed service upgrade itself and then write tests against that than the following tests.

>  - Test that service gets updated to a newer version if you call
> maintenanceservice_tmp.exe update where maintenanceservice_tmp.exe is newer
>  - Test that service gets updated to a newer version if you call
> maintenanceservice_tmp.exe install where maintenanceservice_tmp.exe is newer
>  - Test that service does not get updated to an older version if you call
> maintenanceservice_tmp.exe update where maintenanceservice_tmp.exe is older
>  - Test that service does not get updated to an older version if you call
> maintenanceservice_tmp.exe install where maintenanceservice_tmp.exe is older
>  - Test that service gets removed if you call maintenanceservice_tmp.exe
> uninstall
>  - Test that service gets installed if it doesn't exist if you call
> maintenanceservice_tmp.exe install
>  - Test that service does not get installed if it doesn't exist and you call
> maintenanceservice_tmp.exe install

> - Test to make sure uninstall screen has only 1 uninstall icon and not 2
> (should exist only in HKCU or HKLM but not both), I don't think we can test
> this because we don't actually use the installer in tests right?
When I brought this up years ago I got some pushback about modifying the build systems registry keys... perhaps we can now?

I also think the maintenance service uninstall should always be in HKLM.

> - Verify exactly 5 backup logs are kept if you do 6 updates.
If possible on landing day, if not soon afterward
Comment on attachment 578230 [details] [diff] [review]
All maintenance service and related code. v7'.

>diff --git a/toolkit/mozapps/update/nsUpdateService.js b/toolkit/mozapps/update/nsUpdateService.js
>--- a/toolkit/mozapps/update/nsUpdateService.js
>+++ b/toolkit/mozapps/update/nsUpdateService.js
>...
>@@ -76,17 +77,19 @@ const PREF_APP_UPDATE_MODE              
> const PREF_APP_UPDATE_NEVER_BRANCH        = "app.update.never.";
> const PREF_APP_UPDATE_POSTUPDATE          = "app.update.postupdate";
> const PREF_APP_UPDATE_PROMPTWAITTIME      = "app.update.promptWaitTime";
> const PREF_APP_UPDATE_SHOW_INSTALLED_UI   = "app.update.showInstalledUI";
> const PREF_APP_UPDATE_SILENT              = "app.update.silent";
> const PREF_APP_UPDATE_URL                 = "app.update.url";
> const PREF_APP_UPDATE_URL_DETAILS         = "app.update.url.details";
> const PREF_APP_UPDATE_URL_OVERRIDE        = "app.update.url.override";
>-
>+const PREF_APP_UPDATE_SERVICE             = "app.update.service";
nit: app.update.service.enabled

>+const PREF_APP_UPDATE_SERVICE_FAILCOUNT   = "app.update.service.failcount";
>+const PREF_APP_UPDATE_SERVICE_MAXFAIL     = "app.update.service.maxfail";
nit: camel case with first letter lowercase
We already use errors and maxErrors in app update so please use them here as well
app.update.service.errors
app.update.service.maxErrors

> const PREF_PARTNER_BRANCH                 = "app.partner.";
> const PREF_APP_DISTRIBUTION               = "distribution.id";
> const PREF_APP_DISTRIBUTION_VERSION       = "distribution.version";
> 
> const URI_UPDATE_PROMPT_DIALOG  = "chrome://mozapps/content/update/updates.xul";
> const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
> const URI_BRAND_PROPERTIES      = "chrome://branding/locale/brand.properties";
> const URI_UPDATES_PROPERTIES    = "chrome://mozapps/locale/update/updates.properties";
>@@ -122,35 +125,42 @@ const FILE_UPDATE_ACTIVE  = "active-upda
>...
>+// The default number of times the service can fail while 
>+// still considering it for use.
nit: The number of consecutive failures when updating using the service before setting the app.update.service.enabled preference to false.

>+const DEFAULT_MAX_FAIL_COUNT = 10;
>+

>@@ -582,16 +592,38 @@ function readStatusFile(dir) {
>  */
> function writeStatusFile(dir, state) {
>   var statusFile = dir.clone();
>   statusFile.append(FILE_UPDATE_STATUS);
>   writeStringToFile(statusFile, state);
> }
> 
> /**
>+ * Determines if the service should be used to attempt an update
>+ * or not.
>+ *
>+ * @return  true if app.update.service is true and max failures
>+ *          have not been reached.
>+ */
>+function shouldUseService() {
>+#ifdef MOZ_PHOENIX
Before landing we should make sure that this landing doesn't bust Thunderbird or Seamonkey.

What is the plan to make this compatible with Thunderbird and Seamonkey?

I *think* building the service should be controlled by each app with it being opt-in via confvars.sh and with the ability to disable via mozconfig instead of using ifdef's.

>+  // We never want the service to be used unless we have Firefox
>+  var useService = getPref("getBoolPref", PREF_APP_UPDATE_SERVICE, true);
>+  var failCount = getPref("getIntPref", 
>+                          PREF_APP_UPDATE_SERVICE_FAILCOUNT, 0);
>+  var maxFail = getPref("getIntPref", 
>+                        PREF_APP_UPDATE_SERVICE_MAXFAIL, 
>+                        DEFAULT_MAX_FAIL_COUNT);
>+  return useService && failCount < maxFail;
Per discussion, this should just disable the preference and reset the pref. Might make sense to do this when the error code is SERVICE_UPDATE_ERROR.

>+#else
>+  return false;
>+#endif
>+}
>+

>@@ -1394,16 +1426,25 @@ UpdateService.prototype = {
>           prompter.showUpdateError(update);
>           writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
>           return;
>         }
>         else if (update.errorCode == ELEVATION_CANCELED) {
>           writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
>           return;
>         }
>+        else if (update.errorCode == SERVICE_UPDATE_ERROR) {
>+          var failCount = getPref("getIntPref", 
>+                                  PREF_APP_UPDATE_SERVICE_FAILCOUNT, 0);  
>+          failCount++;
>+          Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_FAILCOUNT, 
>+                                    failCount);
>+          writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
>+          return;
>+        }
nit: please clean up the above while you are here by replacing else if () with if () since they return.

Please add a comment regarding this falling back to updating without the service on service update failure and how the pref to use the service is disabled when the counter reaches the limit.
Comment on attachment 578230 [details] [diff] [review]
All maintenance service and related code. v7'.

>diff --git a/toolkit/components/maintenanceservice/servicebase.h b/toolkit/components/maintenanceservice/servicebase.h
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/servicebase.h
>@@ -0,0 +1,42 @@
>...
>+#include <windows.h>
>+#include "prlog.h"
Is prlog still needed?

>+#include "updatelogging.h"
>+
>+BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);

>diff --git a/toolkit/mozapps/update/common/uachelper.h b/toolkit/mozapps/update/common/uachelper.h
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/mozapps/update/common/uachelper.h
>@@ -0,0 +1,57 @@
>...
>+#ifndef _UACHELPER_H_
>+#define _UACHELPER_H_
>+
>+class UACHelper
>+{
>+public:
>+  // Determines if the specified user token is an administrator or not
>+  enum UserType {
>+    LimitedUser, 
>+    AdministratorUACIsOff, 
>+    AdministratorUnelevated, 
>+    AdministratorElevated
AdministratorElevated is a little deceptive since it is also returned when running as admin without UAC. A comment to this affect should be enough.

>+  };
>+
>+  static BOOL IsVistaOrLater();
>+  static HANDLE OpenUserToken(DWORD sessionID);
>+  static HANDLE OpenLinkedToken(HANDLE token);
>+};
>+
>+#endif

>diff --git a/toolkit/mozapps/update/common/updatedefines.h b/toolkit/mozapps/update/common/updatedefines.h
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/mozapps/update/common/updatedefines.h
>@@ -0,0 +1,124 @@
>...
>+# define LOG_S "%S"
>+# define NS_T(str) L ## str
>+// On Windows, _snprintf and _snwprintf don't guarantee null termination. These
>+// macros always leave room in the buffer for null termination and set the end
>+// of the buffer to null in case the string is larger than the buffer. Having
>+// multiple nulls in a string is fine and this approach is simpler (possibly
>+// faster) than calculating the string length to place the null terminator and
>+// truncates the string as _snprintf and _snwprintf do on other platforms.
>+# define snprintf(dest, count, fmt, ...) \
>+  PR_BEGIN_MACRO \
>+  int _count = count - 1; \
>+  _snprintf(dest, _count, fmt, ##__VA_ARGS__); \
>+  dest[_count] = L'\0'; \
_snprintf is for char and not wide char

NS_tsnprintf should also be here and not in updatelogging.h

I haven't looked too hard for others.
Comment on attachment 578230 [details] [diff] [review]
All maintenance service and related code. v7'.

>diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp
>--- a/toolkit/xre/nsUpdateDriver.cpp
>+++ b/toolkit/xre/nsUpdateDriver.cpp
>...
>@@ -46,16 +47,17 @@
> #include "nsAppRunner.h"
> #include "nsILocalFile.h"
> #include "nsCOMPtr.h"
> #include "nsString.h"
> #include "prproces.h"
> #include "prlog.h"
> #include "prenv.h"
> #include "nsVersionComparator.h"
>+#include "mozilla/Preferences.h"
This is no longer needed now that you use update.status... right?

>@@ -475,18 +486,35 @@ ApplyUpdate(nsIFile *greDir, nsIFile *up
>     PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
>   }
> 
>   LOG(("spawning updater process [%s]\n", updaterPath.get()));
> 
> #if defined(USE_EXECV)
>   execv(updaterPath.get(), argv);
> #elif defined(XP_WIN)
>-  if (!WinLaunchChild(updaterPathW.get(), argc, argv))
>-    return;
>+
>+#ifndef MOZ_PHOENIX
See previous comment about confvars.sh and mozconfig

>diff --git a/toolkit/xre/nsWindowsRestart.cpp b/toolkit/xre/nsWindowsRestart.cpp
>--- a/toolkit/xre/nsWindowsRestart.cpp
>+++ b/toolkit/xre/nsWindowsRestart.cpp
>...
> BOOL
>-WinLaunchChild(const PRUnichar *exePath, int argc, PRUnichar **argv);
>+GetUpdateDirectoryPath(WCHAR *path) 
nit: why the mixed use of PRUnichar and WCHAR in this file?

>...
>+
>+/**
>+ * Sets update.status to pending so that the next startup will not use
>+ * the service and instead will attempt an update the with a UAC prompt.
s/update the with/update with/

>+ *
>+ * @param  updateDirPath The path of the update directory
>+ * @return TRUE if successful
>+ */
>+BOOL
>+WriteStatusPending(LPCWSTR updateDirPath)
There is a part of me that dislikes WriteStatus* being in this file. It is simpleset though.

>diff --git a/xpcom/ds/nsIWindowsRegKey.idl b/xpcom/ds/nsIWindowsRegKey.idl
>--- a/xpcom/ds/nsIWindowsRegKey.idl
>+++ b/xpcom/ds/nsIWindowsRegKey.idl
>@@ -42,17 +42,17 @@ native HKEY(HKEY);
> 
> /**
>  * This interface is designed to provide scriptable access to the Windows
>  * registry system ("With Great Power Comes Great Responsibility").  The
>  * interface represents a single key in the registry.
>  *
>  * This interface is highly Win32 specific.
>  */
>-[scriptable, uuid(2555b930-d64f-437e-9be7-0a2cb252c1f4)]
>+[scriptable, uuid(dbb9ce42-99ee-485d-908b-1c95da48ad9f)]
This is an extension breaking change so drivers will need to know about it for it to land on Aurora and we'll likely need to message this.
Comment on attachment 578230 [details] [diff] [review]
All maintenance service and related code. v7'.

>diff --git a/toolkit/mozapps/update/common/updatelogging.h b/toolkit/mozapps/update/common/updatelogging.h
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/mozapps/update/common/updatelogging.h
>@@ -0,0 +1,102 @@
>...
>+#ifndef MAXPATHLEN
>+# ifdef PATH_MAX
>+#  define MAXPATHLEN PATH_MAX
>+# elif defined(MAX_PATH)
>+#  define MAXPATHLEN MAX_PATH
>+# elif defined(_MAX_PATH)
>+#  define MAXPATHLEN _MAX_PATH
>+# elif defined(CCHMAXPATH)
>+#  define MAXPATHLEN CCHMAXPATH
>+# else
>+#  define MAXPATHLEN 1024
>+# endif
>+#endif
Would it make sense if this were in updatedefines.h?

>+
>+#if defined(XP_WIN)
>+#define NS_tsnprintf(dest, count, fmt, ...) \
Should be in updatedefines.h

>diff --git a/toolkit/mozapps/update/common/Makefile.in b/toolkit/mozapps/update/common/Makefile.in
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/mozapps/update/common/Makefile.in
>@@ -0,0 +1,72 @@
>...
>+CPPSRCS = \
>+  updatelogging.cpp \
>+  $(NULL)
>+
>+EXPORTS = updatelogging.h \
>+	updatedefines.h \
nit: spaces

>diff --git a/toolkit/mozapps/update/common/launchwinprocess.cpp b/toolkit/mozapps/update/common/launchwinprocess.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/mozapps/update/common/launchwinprocess.cpp
>@@ -0,0 +1,203 @@
>...
>+/**
>+ * Launch the post update application as the specified user (helper.exe).
>+ * It takes in the path of the callback application to calculate the path
>+ * of helper.exe
Please add that this can launch the post update application once as the service and a second time as the user.

>+ *
>+ * @param  appExe       The path to the callback application binary.
>+ * @param  userToken    The user token to run as, if NULL the current user
>+ *         will be used.
>+ * @param updateInfoDir The directory where update info is stored.
params out of order

>+ */
>+void
>+LaunchWinPostProcess(const WCHAR *appExe,
>+                     const WCHAR *updateInfoDir,
>+                     HANDLE userToken)
>+{

>+  // If we are running PostUpdate twice, once under session 0 and once 
>+  // under the callback session ID, then the first one must be synchronous in
>+  // case the 2nd call adds the uninstall keys under the user before the first
>+  // program adds them to HKLM.  The HKCU uninstall keys will only be set
>+  // if the HKLM ones are not set.
This could prevent the callback app from launching. Not sure what approach to take to fix this though there are a couple. Please file a followup bug.
Comment on attachment 578230 [details] [diff] [review]
All maintenance service and related code. v7'.

>diff --git a/toolkit/components/maintenanceservice/maintenanceservice.cpp b/toolkit/components/maintenanceservice/maintenanceservice.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
>@@ -0,0 +1,360 @@
>...
>+
>+PRLogModuleInfo *gServiceLog = NULL;
Since you are no longer using PRLog please rename.

I still need to finish up the installer review but the context switch is too much for me atm. I'll finish that up in the morning.
(In reply to Robert Strong [:rstrong] (do not email) from comment #413)
> What is the plan to make this compatible with Thunderbird and Seamonkey?
> 
> I *think* building the service should be controlled by each app with it
> being opt-in via confvars.sh and with the ability to disable via mozconfig
> instead of using ifdef's.

Sounds like a good plan, as I'm pretty sure both SeaMonkey and Thunderbird will want to work on being able to use the service, and having app-specific switches buried deep in code ifdefs is suboptimal for working on it. If this is done as a fast followup to the original patch, I think nobody will be offended, though.
Keywords: addon-compat
Whiteboard: [secr:imelven] → [secr:imelven], addon-compat=nsIWindowsRegKey.idl
Keywords: dev-doc-needed
Whiteboard: [secr:imelven], addon-compat=nsIWindowsRegKey.idl → [secr:imelven], addon-compat and dev-doc-needed for nsIWindowsRegKey.idl
(In reply to Robert Strong [:rstrong] (do not email) from comment #415)
> >diff --git a/xpcom/ds/nsIWindowsRegKey.idl b/xpcom/ds/nsIWindowsRegKey.idl
> >--- a/xpcom/ds/nsIWindowsRegKey.idl
> >+++ b/xpcom/ds/nsIWindowsRegKey.idl
> >@@ -42,17 +42,17 @@ native HKEY(HKEY);
> > 
> > /**
> >  * This interface is designed to provide scriptable access to the Windows
> >  * registry system ("With Great Power Comes Great Responsibility").  The
> >  * interface represents a single key in the registry.
> >  *
> >  * This interface is highly Win32 specific.
> >  */
> >-[scriptable, uuid(2555b930-d64f-437e-9be7-0a2cb252c1f4)]
> >+[scriptable, uuid(dbb9ce42-99ee-485d-908b-1c95da48ad9f)]
> This is an extension breaking change so drivers will need to know about it
> for it to land on Aurora and we'll likely need to message this.

FWIW, this IID change is not needed at all, since the vtable of nsIWindowsRegKey does not change by adding constants to the interface.  The add-ons which don't know about these constants will just not use them.  I think we should revert the IID change and remove the addon-compat keyword from the bug.
(In reply to Robert Strong [:rstrong] (do not email) from comment #417)
> Comment on attachment 578230 [details] [diff] [review] [diff] [details] [review]
> All maintenance service and related code. v7'.
> 
> >diff --git a/toolkit/components/maintenanceservice/maintenanceservice.cpp b/toolkit/components/maintenanceservice/maintenanceservice.cpp
> >new file mode 100644
> >--- /dev/null
> >+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
> >@@ -0,0 +1,360 @@
> >...
> >+
> >+PRLogModuleInfo *gServiceLog = NULL;
> Since you are no longer using PRLog please rename.

In fact I think you could remove this entirely.
comment 419 and comment 420 are correct. Good catches Ehsan and thanks!
Keywords: addon-compat
Whiteboard: [secr:imelven], addon-compat and dev-doc-needed for nsIWindowsRegKey.idl → [secr:imelven], dev-doc-needed for nsIWindowsRegKey.idl
To see specific changes in this latest revision:
================================================

forceinstall cmdline (bypasses version check): 
http://hg.mozilla.org/projects/elm/rev/4c4b66553ae9

Registry fallback key for test slaves:
http://hg.mozilla.org/projects/elm/rev/95391f871f9f
http://hg.mozilla.org/projects/elm/rev/5461f619dcf0
http://hg.mozilla.org/projects/elm/rev/2dd635145bc0

Adding a command ID field so we can have work items for different things in the future:
http://hg.mozilla.org/projects/elm/rev/05364936c121

rs review comments implemented from Dec 1st:
http://hg.mozilla.org/projects/elm/rev/2b3fb918b365

Licensing info change from gerv:
http://hg.mozilla.org/projects/elm/rev/c782531839db

rs review comments for pymake fix:
http://hg.mozilla.org/projects/elm/rev/c1f058945540
Attachment #578230 - Attachment is obsolete: true
Attachment #578651 - Flags: review?(robert.bugzilla)
Attachment #578230 - Flags: review?(robert.bugzilla)
Attachment #578230 - Flags: review?(bsmith)
Attachment #578683 - Flags: review?(ted.mielczarek)
Comment on attachment 578683 [details] [diff] [review]
Makefile hooks for signing

The build infrastructure will set MOZ_SIGN_CMD in the environment to enable signing on various platforms as appropriate.

This needs to land anywhere where we need .exe's, .dll's or .mar's signed.
Attachment #578683 - Flags: review?(robert.bugzilla)
Depends on: 707333
Comment on attachment 578230 [details] [diff] [review]
All maintenance service and related code. v7'.

Posted to the patch I was still reviewing

>diff --git a/browser/installer/windows/nsis/maintenanceservice_installer.nsi b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
>new file mode 100644
>--- /dev/null
>+++ b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
>@@ -0,0 +1,244 @@
>...
>+# Required Plugins:
>+# ServicesHelper Mozilla specific plugin that is located in /other-licenses/nsis
>+
>+; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
>+!verbose 3
>+
>+; 7-Zip provides better compression than the lzma from NSIS so we add the files
>+; uncompressed and use 7-Zip to create a SFX archive of it
>+SetDatablockOptimize on
>+SetCompress off
>+CRCCheck on
>+
>+RequestExecutionLevel admin
>+!addplugindir ./
>+
>+; Variables
>+Var PageName
Remove. Not used for the maintenance installer... please remove here and elsewhere.

>+Var TempMaintServiceName

With the changes below you will also need to declare
Var BrandFullNameDA
Var $BrandFullName

>+
>+; Modenr UI
>+!include "MUI2.nsh"
You have both MUI2.nsh and MUI.nsh below. We currently only use MUI.nsh so is MUI2.nsh provide any value? Seems to work fine without it for me.

>+
>+
Remove the extra empty line

>+; Other included files may depend upon these includes!
>+; The following includes are provided by NSIS.
>+!include FileFunc.nsh
>+!include LogicLib.nsh
>+!include MUI.nsh
>+!include WinMessages.nsh
>+!include WinVer.nsh
>+!include WordFunc.nsh
>+
>+!insertmacro GetOptions
>+!insertmacro GetParameters
>+!insertmacro GetSize
>+
>+!define CompanyName "Mozilla Corporation"
Add
!define BrandFullNameInternal ""

>+
>+; The following includes are custom.
>+!include defines.nsi
>+; We keep defines.nsi defined so that we get other things like 
>+; the version number, but we redefine BrandFullName
>+!define MaintFullName "Mozilla Maintenance Service"
>+!undef BrandFullName
>+!define BrandFullName "${MaintFullName}"
>+
>+!include common.nsh
>+!include locales.nsi
>+
Add
VIAddVersionKey "FileDescription" "${MaintFullName} Installer"
VIAddVersionKey "OriginalFilename" "maintenanceservice_installer.exe"

>+; Must be inserted before other macros that use logging
>+!insertmacro _LoggingCommon
>+!insertmacro SetBrandNameVars
>+!insertmacro UpdateShortcutAppModelIDs
None of these are used with the changes

>+Name "${MaintFullName}"
>+OutFile "maintenanceservice_installer.exe"
>+InstallDir "$PROGRAMFILES\${MaintFullName}"
Not necessary... you set InstallDir below.
>+
>+; Get installation folder from registry if available
>+InstallDirRegKey HKCU "Software\Mozilla\MaintenanceService" ""
This isn't right since it uses HKCU and it shouldn't be necessary

>+
>+SetOverwrite on
>+
>+!define MaintUninstallKey \
>+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService"
>+
>+!ifdef HAVE_64BIT_OS
>+  InstallDir "$PROGRAMFILES64\${MaintFullName}\"
I think it would be better to not build on 64 bit until the 64 bit story is figured out. At the very least we shouldn't be able to install via the installer on 64 bit.

>+!else
>+  InstallDir "$PROGRAMFILES32\${MaintFullName}\"
>+!endif
>+ShowInstDetails nevershow
Remove and add
ShowUnInstDetails nevershow

>+
>+################################################################################
>+# Modern User Interface - MUI
>+
>+!define MUI_ICON setup.ico
>+!define MUI_UNICON setup.ico
>+!define MUI_WELCOMEPAGE_TITLE_3LINES
>+!define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
>+
>+;Interface Settings
>+!define MUI_ABORTWARNING
>+
>+; Uninstaller Pages
>+!define MUI_PAGE_CUSTOMFUNCTION_PRE un.preWelcome
No longer necessary with changes below

>+!insertmacro MUI_UNPAGE_WELCOME
>+!insertmacro MUI_UNPAGE_CONFIRM
Remove MUI_UNPAGE_CONFIRM

>+!insertmacro MUI_UNPAGE_INSTFILES
>+!insertmacro MUI_UNPAGE_FINISH
>+
>+################################################################################
>+# Language
>+
>+!insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
>+!verbose push
>+!verbose 3
>+!include "overrideLocale.nsh"
>+!include "customLocale.nsh"
>+!verbose pop
>+
Add
; Set this after the locale files to override it if it is in the locale
; using " " for BrandingText will hide the "Nullsoft Install System..." branding
BrandingText " "

>+Function .onInit
What do you think of preventing it from being installed on Win2K here as well?

>+  SetSilent silent
>+FunctionEnd
>+
>+Function un.onInit
>+  StrCpy $BrandFullNameDA "${MaintFullName}"
Add
StrCpy $BrandFullName "${MaintFullName}"

>+FunctionEnd
>+
>+Function un.preWelcome
>+  StrCpy $PageName "Welcome"
>+  ${If} ${FileExists} "$EXEDIR\core\distribution\modern-wizard.bmp"
>+    Delete "$PLUGINSDIR\modern-wizard.bmp"
>+    CopyFiles /SILENT "$EXEDIR\core\distribution\modern-wizard.bmp" \
>+              "$PLUGINSDIR\modern-wizard.bmp"
>+  ${EndIf}
>+FunctionEnd
Remove un.preWelcome

>+
>+Section "MaintenanceService"
>+  AllowSkipFiles off
>+
>+  ${LogHeader} "Installing Main Files"
Remove. We'll add logging later if necessary

>+
>+  CreateDirectory $INSTDIR
>+  SetOutPath $INSTDIR
>+
>+  ; Stop the maintenance service so we can overwrite any
>+  ; binaries that it uses.
>+  ; 1 for wait for file release, and 30 second timeout
>+  ServicesHelper::Stop "MozillaMaintenance"
Why are you stopping it without first checking if it exists?

>+
>+  ; If we don't have maintenanceservice.exe already installed
>+  ; then keep that name.  If we do use maintenanceservice_tmp.exe
>+  ; which will auto install itself when you call it with the install parameter.
>+  StrCpy $TempMaintServiceName "maintenanceservice.exe"
>+  IfFileExists "$INSTDIR\maintenanceservice.exe" 0 skipAlreadyExists
>+    StrCpy $TempMaintServiceName "maintenanceservice_tmp.exe"
>+  skipAlreadyExists:
>+
>+  ; We always write out a copy and then decide whether to install it or 
>+  ; not via calling its 'install' cmdline which works by version comparison.
>+  CopyFiles "$EXEDIR\maintenanceservice.exe" "$INSTDIR\$TempMaintServiceName"
>+
>+  ; Install the application updater service.
nit: Install the application maintenance service.

>+  ; If a service already exists, the command line parameter will stop the
>+  ; service and only install itself if it is newer than the already installed
>+  ; service.  If successful it will remove the old maintenanceservice.exe
>+  ; and replace it with maintenanceservice_tmp.exe.
>+  ClearErrors
>+  ${GetParameters} $0
>+  ${GetOptions} "$0" "/Upgrade" $0
>+  ${Unless} ${Errors}
Flip the logic so it uses ${If} ${Errors} and installs instead of upgrade immediately below. This is easier to understand and then you don't have to change the end to ${EndUnless}

>+    ; The upgrade cmdline is the same as install except
>+    ; It will fail if the service isn't already installed.
>+    nsExec::Exec '"$INSTDIR\$TempMaintServiceName" upgrade'
>+  ${Else}
>+    nsExec::Exec '"$INSTDIR\$TempMaintServiceName" install'
>+  ${EndIf}
>+
>+  ${GetLongPath} "$INSTDIR" $8
Remove

>+  WriteRegStr HKLM "${MaintUninstallKey}" "DisplayName" "${MaintFullName}"
>+  WriteRegStr HKLM "${MaintUninstallKey}" "UninstallString" \
>+                   '"$INSTDIR\uninstall.exe"'
>+  WriteRegStr HKLM "${MaintUninstallKey}" "DisplayIcon" \
>+                   "$INSTDIR\Uninstall.exe,0"
>+  WriteRegStr HKLM "${MaintUninstallKey}" "DisplayVersion" "${AppVersion}"
>+  WriteRegStr HKLM "${MaintUninstallKey}" "Publisher" "Mozilla"
>+  WriteRegStr HKLM "${MaintUninstallKey}" "Comments" \
>+                   "${BrandFullName} ${AppVersion} (${ARCH} ${AB_CD})"
>+  ${GetSize} "$8" "/S=0K" $R2 $R3 $R4
Use $INSTDIR

>+  WriteRegDWORD HKLM "${MaintUninstallKey}" "EstimatedSize" $R2 
>+
>+  WriteUninstaller "$INSTDIR\Uninstall.exe"
The extimated size would be more accurate if you wrote out the uninstaller before all of this.

>+
>+  ; Write out that a maintenance service was attempted.
>+  ; We do this because on upgrades we will check this value and we only
>+  ; want to install once on the first upgrade to maintenance service.
>+  ; Also write out that we are currently installed, preferences will check
>+  ; this value to determine if we should show the service update pref.
>+  ; Since the Maintenance service can be installed either x86 or x64,
>+  ; always use the 64-bit registry for checking if an attempt was made.
>+  ; *Nothing* should be added under here that modifies the registry
>+  ; unless it restores the registry view.
>+  SetRegView 64
>+  WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Attempted" 1
>+  WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Installed" 1
>+
>+  # The Mozilla/updates directory will have an inherited permission
>+  # which allows any user to write to it.  Work items are written there.
>+  SetShellVarContext all
>+  CreateDirectory "$APPDATA\Mozilla\updates"
>+SectionEnd
>+
>+Section "Uninstall"
>+  ; Delete the service so that no updates will be attempted
>+  nsExec::Exec '"$INSTDIR\maintenanceservice.exe" uninstall'
>+
>+  Delete /REBOOTOK "$INSTDIR\maintenanceservice.exe"
>+  Delete /REBOOTOK "$INSTDIR\maintenanceservice_tmp.exe"
>+  Delete /REBOOTOK "$INSTDIR\Uninstall.exe"
>+  RMDir /REBOOTOK "$INSTDIR"
Just thought that it would be better to rename the existing files to temporary names and then delete them so if someone tries to install after uninstalling when the files are in use they can still install.
If there are problems with building maintenance_installer.nsi please let me know. I had to modify it as I went through it to clean things up.
There are no problems as of today on my machine, elm, or try. But maybe once I make changes :)
Thanks for the feedback I'll implement them on the train ride shortly.
I meant with the changes... I may have forgotten to add some changes to the review comments.
All of your review comments worked great, thanks rs.
I also tested uninstalling with files in use with exclusively opened files w/ and w/o FILE_SHARE_DELETE (which is what gives you the ability to rename an open file).  Everything works.

New updates in this patch:

Windows 2000 first time upgrades now do not try to run the maintenanceservice installer:
http://hg.mozilla.org/projects/elm/rev/9827130176d7

rs maintenance service review comments:
http://hg.mozilla.org/projects/elm/rev/10336fdea2f0
Attachment #578651 - Attachment is obsolete: true
Attachment #578833 - Flags: review?(robert.bugzilla)
Attachment #578651 - Flags: review?(robert.bugzilla)
For rs:

If there are no objections, I will do the build config fixups (instead of #ifdef MOZ_PHOENIX) via opt-in use maintenance service as a follow up bug since we know already the current patch builds with Thunderbird.
I'm ok with that. Please file it now and someone else might have the bandwidth to take it.
I'm going to fix it up in the context of this bug after all over the weekend, I'd like to have a better understanding of it.
Comment on attachment 578651 [details] [diff] [review]
All maintenance service and related code. v8

>diff --git a/browser/installer/windows/nsis/installer.nsi b/browser/installer/windows/nsis/installer.nsi
>--- a/browser/installer/windows/nsis/installer.nsi
>+++ b/browser/installer/windows/nsis/installer.nsi
>...
>@@ -477,16 +508,36 @@ Section "-Application" APP_IDX
>         ; add the log entry without the path since there is no simple way to
>         ; know the correct full path.
>         ${LogMsg} "Added Quick Launch Shortcut: ${BrandFullName}.lnk"
>         GetFunctionAddress $0 AddQuickLaunchShortcut
>         UAC::ExecCodeSegment $0
>       ${EndIf}
>     ${EndUnless}
>   ${EndIf}
>+
>+  ; Allow main Mozilla cert information for updates
>+  ServicesHelper::PathToUniqueRegistryPath "$INSTDIR"
>+  Pop $MaintCertKey
>+  ${If} $MaintCertKey != ""
>+    ; We always use the 64bit registry for certs.
>+    ; This call is ignored on 32-bit systems.
>+    ; *Nothing* should be added under here that modifies the registry
>+    ; unless it restores the registry view.
>+    ; More than one certificate can be specified in a different subfolder
>+    ; for example: $MaintCertKey\1, but each individual binary can be signed
>+    ; with at most one certificate.  A fallback certificate can only be used
>+    ; if the binary is replaced with a different certificate.
>+    SetRegView 64
>+    WriteRegStr HKLM "$MaintCertKey\0" "name" "Mozilla Corporation"
>+    WriteRegStr HKLM "$MaintCertKey\0" "issuer" "Thawte Code Signing CA - G2"
>+    WriteRegStr HKLM "$MaintCertKey\0" "programName" ""
>+    WriteRegStr HKLM "$MaintCertKey\0" "publisherLink" ""
>+    WriteRegStr HKLM "$MaintCertKey\0" "moreInfoLink" "http://www.mozilla.com"
>+  ${EndIf} 
Should be added on update as well

>@@ -794,16 +845,57 @@ Function leaveShortcuts
>     ${MUI_INSTALLOPTIONS_READ} $AddQuickLaunchSC "shortcuts.ini" "Field 4" "State"
>   ${EndUnless}
> 
>   ${If} $InstallType == ${INSTALLTYPE_CUSTOM}
>     Call CheckExistingInstall
>   ${EndIf}
> FunctionEnd
> 
>+Function preComponents
Skip this page if it is already installed and just updrade the existing install.

I forget if there is a check to prevent the install of an older version on top of a newer version of the service?

>+  ; Don't show the custom components page if the
>+  ; user is not an admin
>+  Call IsUserAdmin
>+  Pop $R9
>+  ${If} $R9 != "true"
>+    Abort
>+  ${EndIf}
>+
>+  ; On Windows 2000 we do not install the maintenance service.
>+  ${Unless} ${AtLeastWinXP}
>+    Abort
>+  ${EndIf}
>+
>+  ; If the service already exists, don't show this page
>+  ; We will always install again (which will upgrade)
>+  ; as long as the user is admin
>+  ServicesHelper::IsInstalled "MozillaMaintenance"
>+  Pop $R9
>+  ${If} $R9 == 1
>+    ; The service already exists so don't show this page.
>+    Abort
>+  ${EndIf}
Reverse the above. This way the exists check is first, the OS version check is second, and the is admin check is third

>+
>+  StrCpy $PageName "Components"
>+  ${CheckCustomCommon}
>+  !insertmacro MUI_HEADER_TEXT "$(COMPONENTS_PAGE_TITLE)" "$(COMPONENTS_PAGE_SUBTITLE)"
>+  !insertmacro MUI_INSTALLOPTIONS_DISPLAY "components.ini"
>+FunctionEnd
Comment on attachment 578651 [details] [diff] [review]
All maintenance service and related code. v8

>diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh
>--- a/browser/installer/windows/nsis/shared.nsh
>+++ b/browser/installer/windows/nsis/shared.nsh
>@@ -100,16 +101,37 @@
>   ${FixClassKeys}
>   ${SetUninstallKeys}
> 
>   ; Remove files that may be left behind by the application in the
>   ; VirtualStore directory.
>   ${CleanVirtualStore}
> 
>   ${RemoveDeprecatedFiles}
>+
>+  Call IsUserAdmin
>+  Pop $R0
>+  ${If} $R0 == "true"
>+    ; We check to see if the maintenance service install was already attempted.
>+    ; Since the Maintenance service can be installed either x86 or x64,
>+    ; always use the 64-bit registry for checking if an attempt was made.
>+    SetRegView 64
>+    ReadRegDWORD $5 HKLM "Software\Mozilla\MaintenanceService" "Attempted"
>+    ${If} $5 == ""
See comment below

>+      ; An install of maintenance service was never attempted.
>+      ; We call ExecShell (which is ShellExecute) with the verb "runas"
>+      ; to ask for elevation if the user isn't already elevated.  If the user 
>+      ; is already elevated it will just launch the program.
>+    ExecShell "runas" "$INSTDIR\maintenanceservice_installer.exe"
>+    ${Else}
>+      ; The maintenance service is already installed.
>+      ; Do nothing, the maintenance service will launch the 
>+    ; maintenanceservice_installer.exe /Upgrade itself to do the self update.
>+    ${EndIf}
The cert reg entries need to be added as previously noted.

>@@ -430,20 +452,33 @@
>   ${WriteRegStr2} $TmpVal "$0" "" "${GREVersion}" 0
>   ${WriteRegStr2} $TmpVal "$0" "CurrentVersion" "${AppVersion} (${AB_CD})" 0
> !macroend
> !define SetAppKeys "!insertmacro SetAppKeys"
> 
> ; Add uninstall registry entries. This macro tests for write access to determine
> ; if the uninstall keys should be added to HKLM or HKCU.
> !macro SetUninstallKeys
>+  Call SetUninstallKeysFn
>+!macroend
>+!define SetUninstallKeys "!insertmacro SetUninstallKeys"
>+
>+Function SetUninstallKeysFn
>   StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})"
> 
>   WriteRegStr HKLM "$0" "${BrandShortName}InstallerTest" "Write Test"
>   ${If} ${Errors}
>+    ; If the uninstall keys already exist in HKLM don't create them in HKCU
>+    ReadRegStr $2 "HKLM" $0 "DisplayName"
>+    ${If} $2 != ""
I kind of prefer checking for errors for these kind of checks since it reminds you to ClearErrors. So the above would be

ClearErrors
WriteRegStr HKLM "$0" "${BrandShortName}InstallerTest" "Write Test"
${If} ${Errors}
  ; If the uninstall keys already exist in HKLM don't create them in HKCU
  ClearErrors
  ReadRegStr $2 "HKLM" $0 "DisplayName"
  ${If} $2 != ""
    ClearErrors
    return
${EndIf}

There are other cases of this pattern in the patch.
+Function un.RenameDelete
+  Pop $9
+  ; If the .moz-delete file already exists previously, delete it
+  ; If it doesn't exist, the call is ignored.
+  ; We don't need to pass /REBOOTOK here since it was already marked that way
+  ; if it exists.
+  Delete "$9.moz-delete"
I believe the call to Delete will set ${Errors} when the file doesn't exist. What ever the case please add ClearErrors here.

+  Rename "$9" "$9.moz-delete"
+  ${If} ${Errors}
+    Delete /REBOOTOK "$9"
+  ${Else} 
+    Delete /REBOOTOK "$9.moz-delete"
+  ${EndIf}
+FunctionEnd
Thanks for the review.

Good catch on the upgrade writing the cert reg keys.
I tested upgrades installing the maint service, but didn't test for the service being used on an updates after it was originally installed from an upgrade.

Recent changes to the most recent patch:

- other installer review comments implemented:
  http://hg.mozilla.org/projects/elm/rev/03b48c67a213
- Removed unused enum:
  http://hg.mozilla.org/projects/elm/rev/7f2b129f6b3d
Attachment #578833 - Attachment is obsolete: true
Attachment #578898 - Flags: review?(robert.bugzilla)
Attachment #578833 - Flags: review?(robert.bugzilla)
Emailed QA to add that to litmus by the way.
Question: does this new functionality take into consideration the fact that firefox.exe every now-and-then does not exit and remains memory resident until manually killed?  What happens in that case?  Will update fail?
Forgot one other point before posting:

Firefox's auto update functionality, as is, has long had a flaw that if an update operation is interrupted at certain points, updates become impossible and it cannot self-recover, requiring someone to manually goes into the [some local profile path]\Mozilla\Firefox\Internet\firefox\ and delete all references to the failing update.

Will this service have any capability of update failure self-recovery?
(In reply to IU from comment #439)
If you can provide steps to reproduce please file a bug with the steps and we'll come up with a way to solve this probably with the existing update code.
(In reply to IU from comment #438)
> Question: does this new functionality take into consideration the fact that
> firefox.exe every now-and-then does not exit and remains memory resident
> until manually killed?  What happens in that case?  Will update fail?
Typically what happens in the case where the Firefox code doesn't exit is the update will be applied after firefox.exe is started the next time and for the update code I am fairly certain the behavior will be the same.
Added in option to enable maintenanceservice (disabled by default), but always on for Firefox via confvars.sh.  On x64 native builds it is off by default.

elm changeset:
http://hg.mozilla.org/projects/elm/rev/c2a92b30af62
Attachment #578898 - Attachment is obsolete: true
Attachment #578974 - Flags: review?(robert.bugzilla)
Attachment #578898 - Flags: review?(robert.bugzilla)
Comment on attachment 578284 [details] [diff] [review]
Automated tests (v4)

All I can say is wow regarding these tests. Nicely done
Attachment #578284 - Flags: review?(robert.bugzilla) → review+
Comment on attachment 578974 [details] [diff] [review]
All maintenance service and related code. v11

bbondy, please add a section in removed-files that removes the maintenance service and the maintenance service installer when it is disabled by MOZ_MAINTENANCE_SERVICE.

khuey, could you review the configure changes? It would likely be easiest to review them from this changeset.
http://hg.mozilla.org/projects/elm/rev/c2a92b30af62
Attachment #578974 - Flags: review?(robert.bugzilla)
Attachment #578974 - Flags: review?(khuey)
Attachment #578974 - Flags: review+
Attachment #578683 - Flags: review?(robert.bugzilla) → review+
Depends on: 707666
(In reply to Brian R. Bondy [:bbondy] from comment #442)
> Created attachment 578974 [details] [diff] [review] [diff] [details] [review]
> All maintenance service and related code. v11
> 
> Added in option to enable maintenanceservice (disabled by default), but
> always on for Firefox via confvars.sh.  On x64 native builds it is off by
> default.
> 
> elm changeset:
> http://hg.mozilla.org/projects/elm/rev/c2a92b30af62

There are some places in this patch where you check for both MOZ_MAINTENANCESERVICE and XP_WIN.  I think the latter check is redundant, and can be removed.
I think they could be simplified if I change confvars.sh to have the check instead.
/**
+   * Submit the results of applying the update via telemetry.
+   *
+   * @param  status
+   *         The status of the update as read from the update.status file
+   */
+  _submitTelemetryPing: function AUS__submitTelemetryPing(status) {
+    let result = 0; // 0 means success
+    let parts = status.split(":");
+    if (parts.length > 1) {
+      result = parseInt(parts[1]);
+      Services.telemetry.getHistogramById("UPDATE_STATUS").add(status);
+    }
+  },
This doesn't appear to submit the error code since it result isn't used or 0 for that matter success.
Comment on attachment 578974 [details] [diff] [review]
All maintenance service and related code. v11

Going to go over the patch another time
Attachment #578974 - Flags: review+ → review?(robert.bugzilla)
Recent changes:

If service fails to apply an update, updater.exe should take responsibility to do the service update:
http://hg.mozilla.org/projects/elm/rev/781b30cb6cd0

Fix for problem only on WinXPx64 and Win2003x64 that Simona found:
http://hg.mozilla.org/projects/elm/rev/6199780feb7b

Some build config fixes (newest to oldest):
http://hg.mozilla.org/projects/elm/rev/b320d9246fcd
http://hg.mozilla.org/projects/elm/rev/8d163b60f6ab
http://hg.mozilla.org/projects/elm/rev/41c7c84336d7
http://hg.mozilla.org/projects/elm/rev/8fdb44fa4fb0
http://hg.mozilla.org/projects/elm/rev/b360fb037e1e
http://hg.mozilla.org/projects/elm/rev/0b5cbb81f343
http://hg.mozilla.org/projects/elm/rev/c65c6698f4ed


--- 

khuey: The feedback? is for the review that rs mentioned for the elm revision and the follow up elm revisions I emailed.  On build config option MOZ_MAINTENANCE_SERVICE.
Attachment #578974 - Attachment is obsolete: true
Attachment #579372 - Flags: superreview?
Attachment #579372 - Flags: review+
Attachment #579372 - Flags: feedback?(khuey)
Attachment #578974 - Flags: review?(robert.bugzilla)
Attachment #578974 - Flags: review?(khuey)
Comment on attachment 578683 [details] [diff] [review]
Makefile hooks for signing

Review of attachment 578683 [details] [diff] [review]:
-----------------------------------------------------------------

Looks good, just a few nits.

::: toolkit/mozapps/installer/packager.mk
@@ +527,5 @@
> +endif
> +endif
> +
> +ifdef MOZ_SIGN_PREPARED_PACKAGE_CMD
> +MAKE_PACKAGE    = $(PREPARE_PACKAGE) && $(MOZ_SIGN_PREPARED_PACKAGE_CMD) $(MOZ_PKG_DIR) && $(INNER_MAKE_PACKAGE)

Hard to tell in the diff view, but does this break 80 characters? Does it screw anything up if you use continuations?

@@ +577,1 @@
>  	if test -f $(FREEBL_64INT); then $(SIGN_CMD) $(FREEBL_64INT); fi;

Can you make this formatting suck less while you're here? Like:
SIGN_NSS += \
  ... \ 
  ...

@@ +920,5 @@
> +# If we're signing with gpg, we'll have a bunch of extra detached signatures to
> +# upload. We also want to sign our checksums file
> +SIGN_CHECKSUM_CMD=$(MOZ_SIGN_CMD) -f gpg $(CHECKSUM_FILE)
> +
> +UPLOAD_FILES += $(CHECKSUM_FILE).asc

Does this need QUOTED_WILDCARD? (That protects against potential spaces in filenames as well as ignoring missing files.)

::: toolkit/mozapps/installer/signing.mk
@@ +28,5 @@
> +#
> +# ***** END LICENSE BLOCK *****
> +
> +# We shouldn't sign the first pass of a PGO build
> +ifneq (1,$(MOZ_PROFILE_GENERATE))

ifndef MOZ_PROFILE_GENERATE

@@ +36,5 @@
> +ifeq (WINNT,$(OS_ARCH))
> +MOZ_INTERNAL_SIGNING_FORMAT := signcode
> +MOZ_EXTERNAL_SIGNING_FORMAT := signcode gpg
> +SIGN_INCLUDES := \
> +    '*.dll' \

Two-space indent after continuations, please.
Attachment #578683 - Flags: review?(ted.mielczarek) → review+
Depends on: 708123
Comment on attachment 579242 [details] [diff] [review]
telemetry patch emailed to me from Ehsan

I've filed bug 708123 for this.
Attachment #579242 - Attachment is obsolete: true
Recent changes:

- New error codes, update app.update.service.errors on sign failures, protection against 2 updates at the same time. 
http://hg.mozilla.org/projects/elm/rev/97df5ff013dc
Attachment #579372 - Attachment is obsolete: true
Attachment #579545 - Flags: review?(robert.bugzilla)
Attachment #579372 - Flags: superreview?
Attachment #579372 - Flags: feedback?(khuey)
Depends on: 708153
Priority: -- → P1
Target Milestone: --- → mozilla11
Removed build config from previous patch.
Attachment #579545 - Attachment is obsolete: true
Attachment #579561 - Flags: review?(robert.bugzilla)
Attachment #579545 - Flags: review?(robert.bugzilla)
The build config removed was put in: Bug 708153 - Provide build option for maintenance service
Attachment #578284 - Attachment is obsolete: true
Attachment #579693 - Attachment is patch: true
Attachment #572647 - Attachment is obsolete: true
a/other-licenses/nsis/Contrib/ServicesHelper/Services.cpp

85/**
86 * Determines if the specified service is installed or not
87 * 
88 * @param  stacktop  A pointer to the top of the stack
89 * @param  variables A pointer to the NSIS variables
90 * @return 0 if the service does not exist
91 *         1 if the service does exist
92 *         1 if there was an error.
93 */

-1 if there was an error

146  if (ControlService(serviceHandle, SERVICE_CONTROL_STOP, &status)) {
147    do {
148      Sleep(status.dwWaitHint);
149      // + 10 milliseconds to make sure we always approach maxWaitTime
150      totalWaitTime += (status.dwWaitHint + 10);
151      if (status.dwCurrentState == SERVICE_STOPPED) {
152        break;
153        stopped = true;
154      } else if (totalWaitTime > maxWaitTime) {
155        break;
156      }
157    } while (QueryServiceStatus(serviceHandle, &status));
158  }

Swap L152/L153

a/toolkit/components/maintenanceservice/pathhash.cpp
130BOOL
131CalculateRegistryPathFromFilePath(const LPCWSTR filePath, 
132                                  LPWSTR registryPath)
133{
134  size_t filePathLen = wcslen(filePath); 
135  if (!filePathLen)
136    return FALSE;
137
138  // If the file path ends in a slash, ignore that character
139  if (filePath[filePathLen -1] == L'\\' || 
140      filePath[filePathLen - 1] == L'/') {
141    filePathLen--;
142  }
143
144  // Copy in the full path into our own buffer.
145  // Copying in the extra slash is OK because we calculate the hash
146  // based on the filePathLen which excludes the slash.
147  WCHAR *lowercasePath = new WCHAR[filePathLen + 1];
148  wcscpy(lowercasePath, filePath);
149  _wcslwr(lowercasePath);
150
151  BYTE *hash;
152  DWORD hashSize = 0;
153  if (!CalculateMD5(reinterpret_cast<const char*>(lowercasePath), 

There is an off-by-one if filePath ends in L'\\' or L'/' . L147 ends up allocating the original filePathLen not accounting for the terminator.
Comment on attachment 579693 [details] [diff] [review]
Automated tests (ready for landing)

These tests should be disabled when building without the maintenance service.
Did you work out a way to add back the original service when testing?
(In reply to Robert Strong [:rstrong] (do not email) from comment #457)
> Comment on attachment 579693 [details] [diff] [review]
> Automated tests (ready for landing)
> 
> These tests should be disabled when building without the maintenance service.

Good point.

> Did you work out a way to add back the original service when testing?

No, I've been thinking about it, but I have not come up with a good solution yet.  :/
Depends on: 708854
Remove the hunk for toolkit/components/maintenanceservice/Makefile.in which I accidentally left in the patch.
Attachment #580230 - Attachment is obsolete: true
Attachment #580231 - Flags: review?(robert.bugzilla)
Attachment #580230 - Flags: review?(robert.bugzilla)
Implemented dchan's review comments.

Changes since last patch on elm:
http://hg.mozilla.org/projects/elm/rev/5ca4500115aa
Attachment #579561 - Attachment is obsolete: true
Attachment #580283 - Flags: review?(robert.bugzilla)
Attachment #579561 - Flags: review?(robert.bugzilla)
No longer depends on: 699567
Depends on: 709183
Depends on: 709598
Attached patch Fallback key uninstall fix (obsolete) — Splinter Review
Fallback key was not being uninstalled.  The fallback key is used to store registry info for test slaves.
Attachment #580812 - Flags: review?(robert.bugzilla)
This is a minor fix for XP when running the post process. Before this fix both post process' would be run sync even if the 2nd one should be run async.
Attachment #580816 - Flags: review?(robert.bugzilla)
Attachment #567667 - Flags: ui-review?(faaborg)
Attachment #580231 - Flags: review?(robert.bugzilla) → review+
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

>diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh
>--- a/browser/installer/windows/nsis/shared.nsh
>+++ b/browser/installer/windows/nsis/shared.nsh
>@@ -100,16 +101,50 @@
>   ${FixClassKeys}
>   ${SetUninstallKeys}
> 
>   ; Remove files that may be left behind by the application in the
>   ; VirtualStore directory.
>   ${CleanVirtualStore}
> 
>   ${RemoveDeprecatedFiles}
>+
>+!ifdef MOZ_MAINTENANCE_SERVICE
>+  Call IsUserAdmin
>+  Pop $R0
>+  ${If} $R0 == "true"
>+  ; On Windows 2000 we do not install the maintenance service.
>+  ${AndIf} ${AtLeastWinXP}
Should check if there is write access to HKLM. $TmpVal should equal "HKLM" when this is true.

>+    ; We check to see if the maintenance service install was already attempted.
>+    ; Since the Maintenance service can be installed either x86 or x64,
>+    ; always use the 64-bit registry for checking if an attempt was made.
>+    SetRegView 64
>+    ReadRegDWORD $5 HKLM "Software\Mozilla\MaintenanceService" "Attempted"
>+    SetRegView lastused
>+
>+    ; If the maintenance service is already installed, do nothing.
>+    ; The maintenance service will launch:
>+    ; maintenanceservice_installer.exe /Upgrade itself to do the self update. 
nit: to upgrade the maintenance service if necessary.

>+    ; If the update was done from updater.exe without the service (i.e. service
>+    ; is failing), updater.exe will do the update of the service.
>+    ; The reasons we do not do it here is because we don't want to have to
>+    ; prompt for limited user accounts when the service isn't used and
>+    ; we currently call the PostUpdate twice, once for the user and once
>+    ; for the SYSTEM account.  Also, this would stop the maintenance service
>+    ; and we need a return result back to the service when run that way.
>+    ${If} $5 == ""
>+      ; An install of maintenance service was never attempted.
>+      ; We call ExecShell (which is ShellExecute) with the verb "runas"
>+      ; to ask for elevation if the user isn't already elevated.  If the user 
>+      ; is already elevated it will just launch the program.
>+      ExecShell "runas" "$INSTDIR\maintenanceservice_installer.exe"
Need to add the cert registry key values in PostUpdate!

>+    ${EndIf}
>+  ${EndIf}
>+!endif
>+
> !macroend
> !define PostUpdate "!insertmacro PostUpdate"
> 
> !macro SetAsDefaultAppGlobal
>   ${RemoveDeprecatedKeys}
> 
>   SetShellVarContext all      ; Set SHCTX to all users (e.g. HKLM)
>   ${SetHandlers}
>@@ -430,20 +465,36 @@
>   ${WriteRegStr2} $TmpVal "$0" "" "${GREVersion}" 0
>   ${WriteRegStr2} $TmpVal "$0" "CurrentVersion" "${AppVersion} (${AB_CD})" 0
> !macroend
> !define SetAppKeys "!insertmacro SetAppKeys"
> 
> ; Add uninstall registry entries. This macro tests for write access to determine
> ; if the uninstall keys should be added to HKLM or HKCU.
> !macro SetUninstallKeys
>+  Call SetUninstallKeysFn
>+!macroend
>+!define SetUninstallKeys "!insertmacro SetUninstallKeys"
>+
>+Function SetUninstallKeysFn
>   StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})"
> 
>+  ClearErrors
>   WriteRegStr HKLM "$0" "${BrandShortName}InstallerTest" "Write Test"
>   ${If} ${Errors}
>+    ; If the uninstall keys already exist in HKLM don't create them in HKCU
>+    ClearErrors
>+    ReadRegStr $2 "HKLM" $0 "DisplayName"
>+    ${If} $2 != ""
>+      ClearErrors
>+      return
>+    ${EndIf}
Instead of adding a function just surround the code for this conditional code with ${If} and ${EndIf}

This will prevent adding the same version to a different install location when one is already install which has been the behavior for as long as I have known. At the very least the version information won't get updated :(

I've been thinking about using a hash of the path using the CityHash plugin to handle this but I haven't checked if it works on systems prior to Win 7. A pre-existing problem is that it wouldn't be obvious which install it is referring to since the install directory isn't listed by default in Programs and Features. I think adding the install directory to the uninstall's registry value for Comments should be adequate especially since we also show the install path in the uninstaller's summary page.

Please file a followup bug for this.

>+
>+    ; Otherwise we don't have any keys for this product in HKLM so proceeed
>+    ; to create them in HKCU.
>     StrCpy $1 "HKCU"
>     SetShellVarContext current  ; Set SHCTX to the current user (e.g. HKCU)
>   ${Else}
>     StrCpy $1 "HKLM"
>     SetShellVarContext all     ; Set SHCTX to all users (e.g. HKLM)
>     DeleteRegValue HKLM "$0" "${BrandShortName}InstallerTest"
>   ${EndIf}
> 
>@@ -465,18 +516,17 @@
>   ${GetSize} "$8" "/S=0K" $R2 $R3 $R4
>   ${WriteRegDWORD2} $1 "$0" "EstimatedSize" $R2 0
> 
>   ${If} "$TmpVal" == "HKLM"
>     SetShellVarContext all     ; Set SHCTX to all users (e.g. HKLM)
>   ${Else}
>     SetShellVarContext current  ; Set SHCTX to the current user (e.g. HKCU)
>   ${EndIf}
>-!macroend
>-!define SetUninstallKeys "!insertmacro SetUninstallKeys"
>+FunctionEnd
> 
> ; Add app specific handler registry entries under Software\Classes if they
> ; don't exist (does not use SHCTX).
> !macro FixClassKeys
>   StrCpy $1 "SOFTWARE\Classes"
> 
>   ; File handler keys and name value pairs that may need to be created during
>   ; install or upgrade.
>@@ -542,16 +592,44 @@
>...
>+!ifdef MOZ_MAINTENANCE_SERVICE
>+; Adds maintenance service certificate keys for the install dir.
>+; For the cert to work, it must also be signed by a trusted cert for the user.
>+!macro AddMaintCertKeys
>+  Push $R0
>+  ; Allow main Mozilla cert information for updates
>+  ; This call will push the needed key on the stack
>+  ServicesHelper::PathToUniqueRegistryPath "$INSTDIR"
>+  Pop $R0
>+  ${If} $R0 != ""
>+    ; We always use the 64bit registry for certs.
>+    ; This call is ignored on 32-bit systems.
nit: this comment is so far from what it is referring to it isn't clear what it is referring to. Probably best to just move the above two lines to right before the call to SetRegView.

>+    ; More than one certificate can be specified in a different subfolder
>+    ; for example: $R0\1, but each individual binary can be signed
>+    ; with at most one certificate.  A fallback certificate can only be used
>+    ; if the binary is replaced with a different certificate.
>+    SetRegView 64

I was thinking that the original values should be removed prior to adding the new values since even though only one cert is relevant today multiple certs could be used in the future. This way a downgrade would remove the old values when installing an older version.

>+    WriteRegStr HKLM "$R0\0" "name" "Mozilla Corporation"
>+    WriteRegStr HKLM "$R0\0" "issuer" "Thawte Code Signing CA - G2"
>+    SetRegView lastused
>+    ClearErrors
>+  ${EndIf} 
>+  ; Restore the previously used value back
>+  Pop $R0
>+!macroend
>+!define AddMaintCertKeys "!insertmacro AddMaintCertKeys"
>+!endif
>+
> ; Removes various registry entries for reasons noted below (does not use SHCTX).
> !macro RemoveDeprecatedKeys
>   StrCpy $0 "SOFTWARE\Classes"
>   ; Remove support for launching gopher urls from the shell during install or
>   ; update if the DefaultIcon is from firefox.exe.
>   ${RegCleanAppHandler} "gopher"
> 
>   ; Remove support for launching chrome urls from the shell during install or
>@@ -1015,18 +1093,21 @@ Function SetAsDefaultAppUserHKCU
>     ; as the default browser.
>     ClearErrors
>     ReadRegStr $0 HKLM "Software\RegisteredApplications" "${AppRegName}"
>     ${Unless} ${Errors}
>       AppAssocReg::SetAppAsDefaultAll "${AppRegName}"
>     ${EndUnless}
>   ${EndIf}
>   ${RemoveDeprecatedKeys}
>-
>   ${PinToTaskBar}
>+!ifdef MOZ_MAINTENANCE_SERVICE
>+  ; Add the registry keys for allowed certificates.
>+  ${AddMaintCertKeys}
>+!endif
Why are you calling ${AddMaintCertKeys} in SetAsDefaultAppUserHKCU since it might not have write access to HKLM? Perhaps this is for the non-installer install?

> FunctionEnd
> 
> ; Helper for updating the shortcut application model IDs.
> Function FixShortcutAppModelIDs
>   ${If} ${AtLeastWin7}
>   ${AndIf} "$AppUserModelID" != ""
>     ${UpdateShortcutAppModelIDs} "$INSTDIR\${FileMainEXE}" "$AppUserModelID" $0
>   ${EndIf}
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

>diff --git a/browser/installer/windows/nsis/installer.nsi b/browser/installer/windows/nsis/installer.nsi
>--- a/browser/installer/windows/nsis/installer.nsi
>+++ b/browser/installer/windows/nsis/installer.nsi
>...
>@@ -271,16 +280,33 @@ Section "-Application" APP_IDX
>   ${LogUninstall} "File: \active-update.xml"
>   ${LogUninstall} "File: \install.log"
>   ${LogUninstall} "File: \install_status.log"
>   ${LogUninstall} "File: \install_wizard.log"
>   ${LogUninstall} "File: \updates.xml"
> 
>   ClearErrors
> 
>+!ifdef MOZ_MAINTENANCE_SERVICE
>+  ; Default for installing the maintenance service 
>+  ${If} $InstallMaintenanceService == ""
I think you want to also check if $TmpVal equals HKLM and the installer is able to create the maintenance service directory before trying to add the service (systems with weird permissions). Also, don't show the components pages when this is the case.

>+    Call IsUserAdmin
>+    Pop $R0
>+    ${If} $R0 == "true"
>+    ; On Windows 2000 we do not install the maintenance service.
>+    ${AndIf} ${AtLeastWinXP}
>+      ; The user is an admin so we should default to install service yes
>+      StrCpy $InstallMaintenanceService "1"
>+    ${Else}
>+      ; The user is not admin so we should default to install service no
>+      StrCpy $InstallMaintenanceService "0"
>+    ${EndIf}
>+  ${EndIf}
>+!endif
>+
>   ; Default for creating Start Menu shortcut
>   ; (1 = create, 0 = don't create)
>   ${If} $AddStartMenuSC == ""
>     StrCpy $AddStartMenuSC "1"
>   ${EndIf}
> 
>   ; Default for creating Quick Launch shortcut (1 = create, 0 = don't create)
>   ${If} $AddQuickLaunchSC == ""
>@@ -477,16 +513,21 @@ Section "-Application" APP_IDX
>         ; add the log entry without the path since there is no simple way to
>         ; know the correct full path.
>         ${LogMsg} "Added Quick Launch Shortcut: ${BrandFullName}.lnk"
>         GetFunctionAddress $0 AddQuickLaunchShortcut
>         UAC::ExecCodeSegment $0
>       ${EndIf}
>     ${EndUnless}
>   ${EndIf}
>+
>+!ifdef MOZ_MAINTENANCE_SERVICE
Should this check if $TmpVal equals HKLM before trying to add the cert key values.

>+  ; Add the registry keys for allowed certificates.
>+  ${AddMaintCertKeys}
>+!endif
> SectionEnd
> 
> ; Cleanup operations to perform at the end of the installation.
> Section "-InstallEndCleanup"
>   SetDetailsPrint both
>   DetailPrint "$(STATUS_CLEANUP)"
>   SetDetailsPrint none
>
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

>diff --git a/browser/components/preferences/advanced.js b/browser/components/preferences/advanced.js
>--- a/browser/components/preferences/advanced.js
>+++ b/browser/components/preferences/advanced.js
>@@ -507,16 +507,36 @@ var gAdvancedPane = {
>     // A locked pref is sufficient to disable the radiogroup.
>     radiogroup.disabled = !canCheck || enabledPref.locked || autoPref.locked;
> 
>     var modePref = document.getElementById("app.update.mode");
>     var warnIncompatible = document.getElementById("warnIncompatible");
>     // the warnIncompatible checkbox value is set by readAddonWarn
>     warnIncompatible.disabled = radiogroup.disabled || modePref.locked ||
>                                 !enabledPref.value || !autoPref.value;
>+
>+#ifdef MOZ_MAINTENANCE_SERVICE
>+    // Check to see if the maintenance service is installed.
>+    // If it is don't show the preference at all.
>+    var installed;
>+    try {
>+      Components.utils.reportError("0");
>+      var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
>+                .createInstance(Components.interfaces.nsIWindowsRegKey);
>+      wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
>+               "SOFTWARE\\Mozilla\\MaintenanceService",
>+               wrk.ACCESS_READ | wrk.WOW64_64);
>+      installed = wrk.readIntValue("Installed");
There is another bug about not using the maintenance service when certain reg keys for the install don't exist. This will need to take that into account in that bug if it isn't.

>diff --git a/toolkit/mozapps/installer/windows/nsis/makensis.mk b/toolkit/mozapps/installer/windows/nsis/makensis.mk
>--- a/toolkit/mozapps/installer/windows/nsis/makensis.mk
>+++ b/toolkit/mozapps/installer/windows/nsis/makensis.mk
>@@ -58,16 +58,22 @@ CUSTOM_NSIS_PLUGINS = \
> 	AppAssocReg.dll \
> 	ApplicationID.dll \
> 	CityHash.dll \
> 	InvokeShellVerb.dll \
> 	ShellLink.dll \
> 	UAC.dll \
> 	$(NULL)
> 
>+ifdef MOZ_MAINTENANCE_SERVICE
>+CUSTOM_NSIS_PLUGINS += \
>+	ServicesHelper.dll \
>+	$(NULL)
>+endif
nit: I wouldn't bother with the ifdef here. NSIS does the right thing if it doesn't use the plugin and we don't ifdef it for other instances where the plugin isn't used.

>diff --git a/other-licenses/nsis/Contrib/ServicesHelper/Services.cpp b/other-licenses/nsis/Contrib/ServicesHelper/Services.cpp
>new file mode 100644
>--- /dev/null
>+++ b/other-licenses/nsis/Contrib/ServicesHelper/Services.cpp
>@@ -0,0 +1,272 @@
>...
>+/**
>+ * Determines if the specified service exists or not
>+ *
>+ * @param  serviceName The name of the service to check
>+ * @param  exists      Whether or not the service exists 
>+ * @return TRUE if there were no errors
>+ */
>+static BOOL
>+IsServiceInstalled(LPCWSTR serviceName, BOOL &exists)
>+{
>+  exists = FALSE;
>+
>+  // Get a handle to the local computer SCM database with full access rights.
>+  SC_HANDLE serviceManager = OpenSCManager(NULL, NULL, 
>+                                           SC_MANAGER_ENUMERATE_SERVICE);
>+  if (!serviceManager) {
>+    return FALSE;
>+  }
>+
>+  SC_HANDLE serviceHandle = OpenServiceW(serviceManager, 
>+                                         serviceName, 
>+                                         SERVICE_QUERY_CONFIG);
>+  if (!serviceHandle && GetLastError() != ERROR_SERVICE_DOES_NOT_EXIST) {
>+    CloseServiceHandle(serviceManager);
>+    return FALSE;
>+  } else if (serviceHandle) {
else if not needed after early return

>+    CloseServiceHandle(serviceHandle);
>+    exists = TRUE;
>+  } 
>+
>+  CloseServiceHandle(serviceManager);
>+  return TRUE;
>+}
>+
>+/**
>+ * Determines if the specified service is installed or not
>+ * 
>+ * @param  stacktop  A pointer to the top of the stack
>+ * @param  variables A pointer to the NSIS variables
>+ * @return 0 if the service does not exist
>+ *         1 if the service does exist
>+ *         -1 if there was an error.
>+ */
>+extern "C" void __declspec(dllexport)
>+IsInstalled(HWND hwndParent, int string_size, 
>+            TCHAR *variables, stack_t **stacktop, void *extra)
>+{
>+  TCHAR tmp[MAX_PATH] = { L'\0' };
>+  WCHAR serviceName[MAX_PATH] = { '\0' };
>+  popstring(stacktop, tmp, MAX_PATH);
>+
>+#if !defined(UNICODE)
>+    MultiByteToWideChar(CP_ACP, 0, tmp, -1, serviceName, MAX_PATH);
>+#else
>+    wcscpy(serviceName, tmp);
>+#endif
>+
>+  BOOL serviceInstalled;
>+  if (!IsServiceInstalled(serviceName, serviceInstalled)) {
>+    pushstring(stacktop, L"-1", 3);
>+  } else {
>+    pushstring(stacktop, serviceInstalled ? L"1" : L"0", 2);
Why not use TEXT here and elsewhere? This will work for us since we compiled with UNICODE but this should be fixed since it supports both.
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

>diff --git a/toolkit/components/maintenanceservice/certificatecheck.cpp b/toolkit/components/maintenanceservice/certificatecheck.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/certificatecheck.cpp
>@@ -0,0 +1,301 @@
>...
>+  GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
>+  // Check if the file is signed by something that is trusted.
>+  LONG ret = WinVerifyTrust(NULL, &policyGUID, &trustData);
>+  if (ERROR_SUCCESS == ret) {
>+    // The hash that represents the subject is trusted and there were no
>+    // verification errors.  No publisher nor time stamp chain errors.
>+    LOG(("The file \"%ls\" is signed and the signature was verified.\n",
>+        filePath));
>+      return ERROR_SUCCESS;
>+  } else {
else not needed after early return

>+    DWORD lastError = GetLastError();
>+    LOG(("There was an error validating trust of the certificate for file"
>+         " \"%ls\". Returned: %d, Last error: %d\n", filePath, ret, lastError));
>+    return ret;
>+  }
>+}
>diff --git a/toolkit/components/maintenanceservice/maintenanceservice.cpp b/toolkit/components/maintenanceservice/maintenanceservice.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/maintenanceservice.cpp
>@@ -0,0 +1,369 @@
>...
>+int 
>+wmain(int argc, WCHAR **argv)
>+{
>+  // If command-line parameter is "install", install the service
>+  // or upgrade if already installed
>+  // If command line parameter is "forceinstall", install the service
>+  // even if it is older than what is already installed.
>+  // If command-line parameter is "upgrade", upgrade the service
>+  // but do not install it if it is not already installed.
>+  // If command line parameter is "uninstall", uninstall the service.
>+  // Otherwise, the service is probably being started by the SCM.
>+  bool forceInstall = !lstrcmpi(argv[1], L"forceinstall");
>+  if (!lstrcmpi(argv[1], L"install") || forceInstall) {
>+    WCHAR updatePath[MAX_PATH + 1];
>+    if (GetLogDirectoryPath(updatePath)) {
>+      LogInit(updatePath, L"maintenanceservice-install.log");
>+    }
>+
>+    LOG(("Installing service"));
>+    SvcInstallAction action = InstallSvc;
>+    if (forceInstall) {
>+      action = ForceInstallSvc;
>+      LOG((" with force specified"));
>+    }
>+    LOG(("...\n"));
>+
>+    if (!SvcInstall(action)) {
>+      LOG(("Could not install service (%d)\n", GetLastError()));
>+      LogFinish();
>+      return 1;
>+    }
>+
>+    LOG(("The service was installed successfully\n"));
>+    LogFinish();
>+    return 0;
>+  } else if (!lstrcmpi(argv[1], L"upgrade")) {
else if not needed after early return

>+    WCHAR updatePath[MAX_PATH + 1];
>+    if (GetLogDirectoryPath(updatePath)) {
>+      LogInit(updatePath, L"maintenanceservice-install.log");
>+    }
>+    LOG(("Upgrading service if installed...\n"));
>+    if (!SvcInstall(UpgradeSvc)) {
>+      LOG(("Could not upgrade service (%d)\n", GetLastError()));
>+      LogFinish();
>+      return 1;
>+    }
>+
>+    LOG(("The service was upgraded successfully\n"));
>+    LogFinish();
>+    return 0;
>+  } else if (!lstrcmpi(argv[1], L"uninstall")) {
else if not needed after early return

>+    WCHAR updatePath[MAX_PATH + 1];
>+    if (GetLogDirectoryPath(updatePath)) {
>+      LogInit(updatePath, L"maintenanceservice-uninstall.log");
>+    }
>+    LOG(("Uninstalling service...\n"));
>+    if (!SvcUninstall()) {
>+      LOG(("Could not uninstall service (%d)\n", GetLastError()));
>+      LogFinish();
>+      return 1;
>+    }
>+    LOG(("The service was uninstalled successfully\n"));
>+    LogFinish();
>+    return 0;
>+  }
>...
>+/**
>+ * Obtains the base path where logs should be stored
>+ *
>+ * @param  path      The out buffer for the backup log path of size MAX_PATH +1
>+ * @return TRUE if successful.
>+ */
>+BOOL
>+GetLogDirectoryPath(WCHAR *path) 
>+{
>+  HRESULT hr = SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, 
>+    SHGFP_TYPE_CURRENT, path);
>+  if (FAILED(hr)) {
>+    return FALSE;
>+  }
nit: add a blank line here and elsewhere

>...
>+BackupOldLogs(LPCWSTR basePath, int numLogsToKeep) 
>+{
>+  WCHAR oldPath[MAX_PATH + 1];
>+  WCHAR newPath[MAX_PATH + 1];
>+  for (int i = numLogsToKeep; i >= 1; i--) {
>+    if (!GetBackupLogPath(oldPath, basePath, i -1)) 
>+      continue;
>+    if (!GetBackupLogPath(newPath, basePath, i))
>+      continue;
>+    if (!MoveFileEx(oldPath, newPath, MOVEFILE_REPLACE_EXISTING))
>+      continue;
nit: braces here and elsewhere
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

>diff --git a/toolkit/components/maintenanceservice/pathhash.cpp b/toolkit/components/maintenanceservice/pathhash.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/pathhash.cpp
>@@ -0,0 +1,169 @@
>...
>+static BOOL
>+CalculateMD5(const char *data, DWORD dataSize, 
>+             BYTE **hash, DWORD &hashSize)
>+{
>+  HCRYPTPROV hProv = 0;
>+  HCRYPTHASH hHash = 0;
>+
>+  if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) {
space after if and before (

>+    if (NTE_BAD_KEYSET == GetLastError()) {
>+      // Maybe it doesn't exist, try to create it.
>+      if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 
>+                              CRYPT_NEWKEYSET)) {
>+        return FALSE;
>+      }
>+    } else {
>+      return FALSE;
>+    }
nit: the above would be clearer if you did

if (NTE_BAD_KEYSET != GetLastError()) {
  return FALSE;
}

if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 
                        CRYPT_NEWKEYSET)) {
  return FALSE;
}

>+  }
>+
>+  if(!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
space after if and before (

>+    return FALSE;
>+  }
>+
>+  if(!CryptHashData(hHash, reinterpret_cast<const BYTE*>(data), 
>+                    dataSize, 0)) {
space after if and before (

>+    return FALSE;
>+  }
>+
>+  DWORD dwCount = sizeof(DWORD);
>+  if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&hashSize, 
>+                        &dwCount, 0)) {
space after if and before (

>+    return FALSE;
>+  }
>+  
>+  *hash = new BYTE[hashSize];
>+  ZeroMemory(*hash, hashSize);
>+  if(!CryptGetHashParam(hHash, HP_HASHVAL, *hash, &hashSize, 0)) {
space after if and before (

>+    return FALSE;
>+  }
>+
>+  if(hHash) {
space after if and before (

>+    CryptDestroyHash(hHash);
>+  }
>+
>+  if(hProv) {
space after if and before (

>+    CryptReleaseContext(hProv,0);
>+  }
>+
>+  return TRUE;
>+}
>+
>+/**
>+ * Converts a file path into a unique registry location for cert storage
>+ *
>+ * @param  filePath     The input file path to get a registry path from
>+ * @param  registryPath A buffer to write the registry path to, must 
>+ *         be of size in WCHARs MAX_PATH + 1
>+ * @return TRUE if successful
>+*/
>+BOOL
>+CalculateRegistryPathFromFilePath(const LPCWSTR filePath, 
>+                                  LPWSTR registryPath)
>+{
>+  size_t filePathLen = wcslen(filePath); 
>+  if (!filePathLen)
>+    return FALSE;
nit: always use braces on new code as you have done elsewhere
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

>diff --git a/toolkit/components/maintenanceservice/serviceinstall.cpp b/toolkit/components/maintenanceservice/serviceinstall.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/components/maintenanceservice/serviceinstall.cpp
>@@ -0,0 +1,416 @@
>...
>+BOOL
>+SvcInstall(SvcInstallAction action)
>+{
>+  // Get a handle to the local computer SCM database with full access rights.
>+  nsAutoServiceHandle schSCManager(OpenSCManager(NULL, NULL, 
>+                                                 SC_MANAGER_ALL_ACCESS));
>+  if (!schSCManager) {
>+    LOG(("Could not open service manager.  (%d)\n", GetLastError()));
>+    return FALSE;
>+  }
>+
>+  WCHAR newServiceBinaryPath[MAX_PATH + 1];
>+  if(!GetModuleFileNameW(NULL, newServiceBinaryPath, 
>+                         sizeof(newServiceBinaryPath) / 
>+                         sizeof(newServiceBinaryPath[0]))) {
space after if and before (

>+    LOG(("Could not obtain module filename when attempting to "
>+         "install service. (%d)\n",
>+         GetLastError()));
>+    return FALSE;
>+  }
>+
>+  // Check if we already have an open service
>+  nsAutoServiceHandle schService(OpenServiceW(schSCManager, 
>+                                              SVC_NAME, 
>+                                              SERVICE_ALL_ACCESS));
>+  DWORD lastError = GetLastError();
>+  if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError) {
>+    // The service exists but we couldn't open it
>+    LOG(("Could not open service.  (%d)\n", GetLastError()));
>+    return FALSE;
>+  }
>+  
>+  if(schService) {
space after if and before (

>...
>+    DWORD existingA, existingB, existingC, existingD;
>+    DWORD newA, newB, newC, newD; 
>+    BOOL obtainedExistingVersionInfo = 
>+      GetVersionNumberFromPath(serviceConfig.lpBinaryPathName, 
>+                               existingA, existingB, 
>+                               existingC, existingD);
>+    if(!GetVersionNumberFromPath(newServiceBinaryPath, newA, 
>+                                 newB, newC, newD)) {
space after if and before (

>+      LOG(("Could not obtain version number from new path\n"));
>+      return FALSE;
>+    }
>...
>+  // Delete the service or mark it for deletion
>+  BOOL deleted = DeleteService(schService);
>+  if(!deleted) {
space after if and before (
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

>diff --git a/toolkit/mozapps/update/common/launchwinprocess.cpp b/toolkit/mozapps/update/common/launchwinprocess.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/mozapps/update/common/launchwinprocess.cpp
>@@ -0,0 +1,263 @@
>...
>+BOOL
>+PathGetSiblingFilePath(LPWSTR destinationBuffer, 
>+                       LPCWSTR siblingFilePath, 
>+                       LPCWSTR newFileName)
>+{
>+  if (wcslen(siblingFilePath) >= MAX_PATH) {
>+    return FALSE;
>+  }
>+
>+  wcscpy(destinationBuffer, siblingFilePath);
>+  if (!PathRemoveFileSpecW(destinationBuffer))
>+    return FALSE;
nit: add braces as you have done elsewhere

>+
>+  if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) {
>+    return FALSE;
>+  }
>+
>+  return PathAppendSafe(destinationBuffer, newFileName);
>+}
>+
>+/**
>+ * Launch the post update application as the specified user (helper.exe).
>+ * It takes in the path of the callback application to calculate the path
>+ * of helper.exe.  For service updates this is called from both the system
>+ * account and the current user account.
>+ *
>+ * @param  appExe        The path to the callback application binary.
>+ * @param  updateInfoDir The directory where update info is stored.
>+ * @param  userToken     The user token to run as, if NULL the current user
>+ *         will be used.
nit: alignment. Please align with "The" unless I am mistaken with the javadoc style guide mentioned previously.

>diff --git a/toolkit/mozapps/update/common/uachelper.cpp b/toolkit/mozapps/update/common/uachelper.cpp
>new file mode 100644
>--- /dev/null
>+++ b/toolkit/mozapps/update/common/uachelper.cpp
>@@ -0,0 +1,101 @@
>...
>+HANDLE
>+UACHelper::OpenLinkedToken(HANDLE token) 
>+{
>+  // Magic below...
>+  // UAC creates 2 tokens.  One is the restricted token which we have.
>+  // the other is the UAC elevated one. Since we are running as a service
>+  // as the system account we have access to both.
>+  TOKEN_LINKED_TOKEN tlt;
>+  HANDLE hNewLinkedToken = NULL;
>+  DWORD len;
>+  if(GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, 
>+                         &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) {
space after if and before (
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

>diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
>--- a/toolkit/mozapps/update/updater/updater.cpp
>+++ b/toolkit/mozapps/update/updater/updater.cpp
>@@ -2030,17 +1824,29 @@ int NS_main(int argc, NS_tchar **argv)
>   }
> #endif /* XP_WIN */
> 
>   LogFinish();
> 
>   if (argc > callbackIndex) {
> #if defined(XP_WIN)
>     if (gSucceeded) {
>-      LaunchWinPostProcess(argv[callbackIndex]);
>+      LaunchWinPostProcess(argv[callbackIndex], gSourcePath, NULL);
>+      // The service update will only be executed if it is already installed.
>+      // For first time installs of the service, the install will happen from
>+      // the PostUpdate process. We do the service update process here 
>+      // because it's possible we are updating with updater.exe without the 
>+      // service if the service failed to apply the update even know it is
I think it is clear enough without "even know it is installed" or you can fix it.

>+      // installed. We want to update the service to a newer version in that
>+      // case. If we are not running through the service, then MOZ_SESSION_ID
>+      // will not exist.
>+      WCHAR *sessionIDStr = _wgetenv(L"MOZ_SESSION_ID");
>+      if (!sessionIDStr) {
>+        StartServiceUpdate(argc, argv);
>+      }
>     }
>     EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
> #endif /* XP_WIN */
> #ifdef XP_MACOSX
>     if (gSucceeded) {
>       LaunchMacPostProcess(argv[callbackIndex]);
>     }
> #endif /* XP_MACOSX */
>diff --git a/toolkit/xre/nsWindowsRestart.cpp b/toolkit/xre/nsWindowsRestart.cpp
>--- a/toolkit/xre/nsWindowsRestart.cpp
>+++ b/toolkit/xre/nsWindowsRestart.cpp
>@@ -220,84 +232,424 @@ FreeAllocStrings(int argc, PRUnichar **a
>...
>+EnsureWindowsServiceRunning() {
>...
>+  if (ssp.dwCurrentState == SERVICE_STOPPED) {
>+    if (!StartService(service, 0, NULL)) {
>+      return FALSE;
>+    }
>+
>+    // Make sure we can get into a started state without waiting too long.
>+    // This usually starts instantly but the extra code is just in case it
>+    // takes longer.
>+    DWORD totalWaitTime = 0;
>+    static const int maxWaitTime = 1000 * 5; // Never wait more than 5 seconds
>+    while (QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
>+                                sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
>+      if (ssp.dwCurrentState == SERVICE_RUNNING) {
>+        break;
>+      } else if (ssp.dwCurrentState == SERVICE_START_PENDING &&
>+                 totalWaitTime > maxWaitTime) {
else if not needed after break

>+        // We will probably eventually start, but we can't wait any longer.
>+        break;
>+      } else if (ssp.dwCurrentState != SERVICE_START_PENDING) {
else if not needed after break

>+        return FALSE;
>+      }
>+
>+      Sleep(ssp.dwWaitHint);

>...
>+BOOL
>+WinLaunchServiceCommand(const PRUnichar *exePath, int argc, char **argv)
> {
>   PRUnichar** argvConverted = new PRUnichar*[argc];
>   if (!argvConverted)
>     return FALSE;
nit: please add braces

>+BOOL
>+WinLaunchChild(const PRUnichar *exePath, 
>+               int argc, char **argv, 
>+               HANDLE userToken)
>+{
>+  PRUnichar** argvConverted = new PRUnichar*[argc];
>+  if (!argvConverted)
>+    return FALSE;
nit: please add braces

>...
>-WinLaunchChild(const PRUnichar *exePath, int argc, PRUnichar **argv)
>+WinLaunchChild(const PRUnichar *exePath, 
>+               int argc, 
>+               PRUnichar **argv, 
>+               HANDLE userToken)
> {
>   PRUnichar *cl;
>   BOOL ok;
> 
>   cl = MakeCommandLine(argc, argv);
>   if (!cl)
>     return FALSE;
nit: please add braces while you are here
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

>diff --git a/toolkit/mozapps/update/nsUpdateService.js b/toolkit/mozapps/update/nsUpdateService.js
>--- a/toolkit/mozapps/update/nsUpdateService.js
>+++ b/toolkit/mozapps/update/nsUpdateService.js
>@@ -582,16 +595,32 @@ function readStatusFile(dir) {
>...
>+function shouldUseService() {
>+#ifdef MOZ_MAINTENANCE_SERVICE
>+  return getPref("getBoolPref", 
>+                 PREF_APP_UPDATE_SERVICE_ENABLED, false);
I'm tempted to have the default return value be true but I think this is better at least for now.

>+#else
>+  return false;
>+#endif
>+}
>+
>+/**
> #  Writes the update's application version to a file in the patch directory. If
> #  the update doesn't provide application version information via the
> #  appVersion attribute the string "null" will be written to the file.
> #  This value is compared during startup (in nsUpdateDriver.cpp) to determine if
> #  the update should be applied. Note that this won't provide protection from
> #  downgrade of the application for the nightly user case where the application
> #  version doesn't change.
> #  @param   dir
>@@ -1390,17 +1419,42 @@ UpdateService.prototype = {
>       update.state = ary[0];
>       if (update.state == STATE_FAILED && ary[1]) {
>         update.errorCode = parseInt(ary[1]);
>         if (update.errorCode == WRITE_ERROR) {
>           prompter.showUpdateError(update);
>           writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
>           return;
>         }
>-        else if (update.errorCode == ELEVATION_CANCELED) {
>+        if (update.errorCode == ELEVATION_CANCELED) {
>+          writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
>+          return;
>+        }
nit: add a blank line

>+        if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED ||
>+            update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS ||
>+            update.errorCode == SERVICE_UPDATER_SIGN_ERROR || 
>+            update.errorCode == SERVICE_CALLBACK_SIGN_ERROR) {
>+          var failCount = getPref("getIntPref", 
>+                                  PREF_APP_UPDATE_SERVICE_ERRORS, 0);
>+          var maxFail = getPref("getIntPref", 
>+                                PREF_APP_UPDATE_SERVICE_MAX_ERRORS, 
>+                                DEFAULT_SERVICE_MAX_ERRORS);
>+
>+          // As a safety, when the service reaches maximum failures, it will
>+          // disable itself and fallback to using the normal update mechanism
>+          // without the service.
>+          if (failCount >= maxFail) {
>+            failCount = 0;
>+            Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
Don't bother setting failCount to 0 and instead just
Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);

>+          } else {
>+            failCount++;
>+          }
>+          Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS, 
>+                                    failCount);
Move this into the else block above
Comment on attachment 580283 [details] [diff] [review]
All maintenance service and related code. v14

Looks good and most of the comments were just nits. The only thing I think I need to see again is the NSIS code. r=me on everything but the NSIS code. That should be it with this beast.

btw: lots of this code is changed in other patches and I tried to avoid focusing on the areas that have recently changed during this review.
Attachment #580283 - Flags: review?(robert.bugzilla) → review-
Comment on attachment 580812 [details] [diff] [review]
Fallback key uninstall fix

Has the existence and use of the fallback key been reviewed by security? r+ on this patch with that.
Attachment #580812 - Flags: review?(robert.bugzilla) → review+
Attachment #580816 - Flags: review?(robert.bugzilla) → review+
Attachment #580812 - Attachment is obsolete: true
Implemented review comments.
Attachment #580283 - Attachment is obsolete: true
Attachment #580816 - Attachment is obsolete: true
Attachment #581882 - Flags: review?(robert.bugzilla)
Wei, Hong, please make sure you understand this feature, and how it changes how the installer and updater works. I think this is likely to have a big effect on your work on the Mozilla China installer. Pay close attention to the bugs in the "depends on" list of this bug too.
Comment on attachment 581882 [details] [diff] [review]
All maintenance service and related code. v15

>diff --git a/browser/installer/windows/nsis/installer.nsi b/browser/installer/windows/nsis/installer.nsi
>--- a/browser/installer/windows/nsis/installer.nsi
>+++ b/browser/installer/windows/nsis/installer.nsi
>...
>+  ; Only show the maintenance service page if we have write access to HKLM
>+  ClearErrors
>+  WriteRegStr HKLM "Software\Mozilla" \
>+              "${BrandShortName}InstallerTest" "Write Test"
>+  ${If} ${Errors}
>+    ClearErrors
>+    Abort
>+  ${Else}
>+    DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
>+    ClearErrors
nit: I'm fine with leaving this here in case there is an error but it shouldn't be necessary.

>diff --git a/browser/installer/windows/nsis/maintenanceservice_installer.nsi b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
>new file mode 100644
>--- /dev/null
>+++ b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
>@@ -0,0 +1,283 @@
I forgot to ask you to add the begin license block for this file
# ***** BEGIN LICENSE BLOCK *****


>+; Variables
>+Var TempMaintServiceName
>+Var FallbackKey
Not used anymore unless I'm mistaken

>+  ; Included here for debug purposes only.  
>+  ; These keys are used to bypass the installation dir is a valid installation
>+  ; check from the service so that tests can be run.
>+  ; WriteRegStr HKLM "${FallbackKey}\0" "name" "Mozilla Corporation"
>+  ; WriteRegStr HKLM "${FallbackKey}\0" "issuer" "Thawte Code Signing CA - G2"
Might be better to add the name and issuer as !define's in defines.nsh.in. I'm ok with this as is though.

>diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh
>--- a/browser/installer/windows/nsis/shared.nsh
>+++ b/browser/installer/windows/nsis/shared.nsh
>...
>+    ; We check to see if the maintenance service install was already attempted.
>+    ; Since the Maintenance service can be installed either x86 or x64,
>+    ; always use the 64-bit registry for checking if an attempt was made.
>+    SetRegView 64
>+    ReadRegDWORD $5 HKLM "Software\Mozilla\MaintenanceService" "Attempted"
>+    SetRegView lastused
>+
>+    ; If the maintenance service is already installed, do nothing.
>+    ; The maintenance service will launch:
>+    ; maintenanceservice_installer.exe /Upgrade to upgrade the maintenance
>+    ; service if necessary.   If the update was done from updater.exe without 
>+    ; the service (i.e. service is failing), updater.exe will do the update of 
>+    ; the service.  The reasons we do not do it here is because we don't want 
>+    ; to have to prompt for limited user accounts when the service isn't used 
>+    ; and we currently call the PostUpdate twice, once for the user and once
>+    ; for the SYSTEM account.  Also, this would stop the maintenance service
>+    ; and we need a return result back to the service when run that way.
>+    ${If} $5 == ""
nit: this isn't a problem with the code as is but if the registry value doesn't exist you should ClearErrors here just in case

>+      ; An install of maintenance service was never attempted.
>+      ; We call ExecShell (which is ShellExecute) with the verb "runas"
>+      ; to ask for elevation if the user isn't already elevated.  If the user 
>+      ; is already elevated it will just launch the program.
>+      ExecShell "runas" "$INSTDIR\maintenanceservice_installer.exe"
>+    ${EndIf}
>+  ${EndIf}
>+!endif

>@@ -432,48 +472,60 @@
> !macroend
> !define SetAppKeys "!insertmacro SetAppKeys"
> 
> ; Add uninstall registry entries. This macro tests for write access to determine
> ; if the uninstall keys should be added to HKLM or HKCU.
> !macro SetUninstallKeys
>   StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})"
> 
>+  StrCpy $2 ""
>+  ClearErrors
>   WriteRegStr HKLM "$0" "${BrandShortName}InstallerTest" "Write Test"
>   ${If} ${Errors}
>-    StrCpy $1 "HKCU"
>-    SetShellVarContext current  ; Set SHCTX to the current user (e.g. HKCU)
>+    ; If the uninstall keys already exist in HKLM don't create them in HKCU
>+    ClearErrors
>+    ReadRegStr $2 "HKLM" $0 "DisplayName"
>+    ${If} $2 == ""
>+      ; Otherwise we don't have any keys for this product in HKLM so proceeed
>+      ; to create them in HKCU.
>+      StrCpy $1 "HKCU"
>+      SetShellVarContext current  ; Set SHCTX to the current user (e.g. HKCU)
>+    ${EndIf}
>+    ClearErrors
>   ${Else}
>     StrCpy $1 "HKLM"
>     SetShellVarContext all     ; Set SHCTX to all users (e.g. HKLM)
>     DeleteRegValue HKLM "$0" "${BrandShortName}InstallerTest"
>   ${EndIf}
> 
>-  ${GetLongPath} "$INSTDIR" $8
Please file a bug as mentioned in comment #465 and reference it here

>+  ${If} $2 == ""
>+    ${GetLongPath} "$INSTDIR" $8
> 
>-  ; Write the uninstall registry keys
>diff --git a/xpcom/base/nsWindowsHelpers.h b/xpcom/base/nsWindowsHelpers.h
>new file mode 100644
>--- /dev/null
>+++ b/xpcom/base/nsWindowsHelpers.h
>@@ -0,0 +1,118 @@
>...
>+public:
>+  RawRef get() const 
>+  {
>+    return mRawRef;
>+  }
>+
>+  static void Release(RawRef aRawRef) 
>+  {
>+    if(aRawRef != NULL && aRawRef != INVALID_HANDLE_VALUE) {
nit: space after if and before (

>+      CloseHandle(aRawRef);
>+    }
>+  }
>+  RawRef mRawRef;
>+};
>diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
>--- a/toolkit/mozapps/update/updater/updater.cpp
>+++ b/toolkit/mozapps/update/updater/updater.cpp
>...
>@@ -1622,18 +1396,37 @@ LaunchCallbackApp(const NS_tchar *workin
>   // passes the working directory as an --environ: command line argument.
>   if(NS_tchdir(workingDir) != 0)
nit: could you please add a space after if and before (

>     LOG(("Warning: chdir failed\n"));
> 
> #if defined(USE_EXECV)
>   execv(argv[0], argv);
> #elif defined(XP_MACOSX)
>   LaunchChild(argc, argv);
>+#elif defined(MOZ_MAINTENANCE_SERVICE)
>+  // If updater.exe is run as session ID 0 and we have a session ID

woohoo!
Attachment #581882 - Flags: review?(robert.bugzilla) → review+
Just carry forward my r+
Blocks: 711044
Blocks: 711054
Implemented review comments.
Attachment #581882 - Attachment is obsolete: true
Attachment #581990 - Flags: review+
Depends on: 711140
Blocks: 711393
Blocks: 711475
Blocks: 711505
Depends on: 711682
Depends on: 711692
Depends on: 711726
Blocks: 711792
Depends on: 711834
Depends on: 708697
Status: ASSIGNED → RESOLVED
Closed: 12 years ago
Resolution: --- → FIXED
Target Milestone: mozilla11 → mozilla12
No longer blocks: 701372
Depends on: 715465
Depends on: 715489
Can you please update https://wiki.mozilla.org/Windows_Service_Silent_Update to the decisions you've made:
"The service is currently planned to be stopped until it is needed"
"The name of the service is subject to change"
"We plan to have only one Windows service"

"The service will be installed for users automatically via software update."
Today's Nightly contains maintenanceservice.exe, but the "Use a background service to install updates" checkbox in Options-Advanced-Update is hidden.
> Can you please update https://wiki.mozilla.org/Windows_Service_Silent_Update
> to the decisions you've made:
> "The service is currently planned to be stopped until it is needed"
> "The name of the service is subject to change"
> "We plan to have only one Windows service"

I have been updating the documentation throughout development, but I do have it on the top of my list to re-update with the final information that is landed.  I have an important patch not directly related to this one to submit for review ahead of updating the documentation, but it should be updated this week.


> "The service will be installed for users automatically via software update."
> Today's Nightly contains maintenanceservice.exe, but the "Use a background 
> service to install updates" checkbox in Options-Advanced-Update is hidden.

It sounds like it wasn't installed for you.  The files will be in your firefox directory even if the service is not installed.  There are reasons that it will not install purposely though.

- Which operating system are you using? 
- Are you running as an admin?
- Have you ever installed a previous build and manually uninstalled? It will only be installed on update on the first install.
- Is the service installed on your machine? From a command prompt run sc query MozillaMaintenance
An additional question to the previous list:
- Are you using an x86 build or an x64 build?
Depends on: 715746
Depends on: 715749
Blocks: 715803
Depends on: 715876
Depends on: 715910
I updated the service documentation throughout, it can be found here:
https://wiki.mozilla.org/Windows_Service_Silent_Update
Depends on: 716126
Depends on: 716237
(In reply to Brian R. Bondy [:bbondy] from comment #483 and #484)
> It sounds like it wasn't installed for you.  The files will be in your
> firefox directory even if the service is not installed.  There are reasons
> that it will not install purposely though.
> 
> - Which operating system are you using? 
Windows 7 64bit.
> - Are you running as an admin?
Yes.
> - Have you ever installed a previous build and manually uninstalled? It will
> only be installed on update on the first install.
Quite possibly.
> - Is the service installed on your machine? From a command prompt run sc
> query MozillaMaintenance
No.
> - Are you using an x86 build or an x64 build?
x86.
I've also granted my windows user account full access to the nightly app dir to get rid of the UAC warnings long ago.
Thanks for updating the doc.

From reading that, you might have another question:
In regedit, check for the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService and value Attempted=1.
I don't have a HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla key at all, only the usual HKEY_CURRENT_USER\Software\Mozilla keys.

If I have uninstalled a previous version of Nightly, it was not recently, but rather several months ago.
It seems the install of the maintenance service was not attempted at all if the service does not exist and you don't have that registry setting.  This is the first I've heard of that.  Are you possibly using a different branch than the mozilla-central Nightly? Such as the UX Nightly?  If not please post a new bug with all the info from the previous few comments.
Depends on: 716492
I'm also having trouble getting the MozillaMaintenance service to work.

(In reply to Brian R. Bondy [:bbondy] from comment #483)
> - Which operating system are you using? 

Win7 X64, but the Nightly is 32 bit.

> - Are you running as an admin?

My account has admin privileges and brings up the UAC dialog for every update, yes.

> - Have you ever installed a previous build and manually uninstalled? It will
> only be installed on update on the first install.

Not to my knowledge. I installed nightly here a few months ago and updated it ever since.

> - Is the service installed on your machine? From a command prompt run sc
> query MozillaMaintenance

It is installed, but not running. The output is:

SERVICE_NAME: MozillaMaintenance 
        TYPE               : 10  WIN32_OWN_PROCESS  
        STATE              : 1  STOPPED 
        WIN32_EXIT_CODE    : 1077  (0x435)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0

And yes, I'm still getting the UAC on every Nightly update.
MarcoZ sounds like you want to be in Bug 715910.
Depends on: 716915
Depends on: 719947
Depends on: 720230
Depends on: 721182
Comment on attachment 578683 [details] [diff] [review]
Makefile hooks for signing

It would be great to port this functionality to the ESR branch what would allow RelEng dropping manual signing. This feature has been tested for 4 betas and worked just fine.
Attachment #578683 - Flags: approval-mozilla-esr10?
(In reply to Rail Aliiev [:rail] from comment #492)
> Comment on attachment 578683 [details] [diff] [review]
> Makefile hooks for signing
> 
> It would be great to port this functionality to the ESR branch what would
> allow RelEng dropping manual signing. This feature has been tested for 4
> betas and worked just fine.
Are you certain that you have the right bug? This bug is only on Aurora and has never made it into a beta yet plus there are a ton of other bugs / patches involved beyond this bug. Also, I don't think this bug is necessary in order for releng to port the automatic signing.
Comment on attachment 578683 [details] [diff] [review]
Makefile hooks for signing

[Triage Comment]
Looks good to me, will be great to get rid of manual signing. Please land to esr branch today preferably, in time for the go-to-build on Friday
Attachment #578683 - Flags: approval-mozilla-esr10? → approval-mozilla-esr10+
My mistake... I assumed the signing changes were ll part of a separate bug.
Comment on attachment 578683 [details] [diff] [review]
Makefile hooks for signing

Transplanted to mozilla-esr10:
http://hg.mozilla.org/releases/mozilla-esr10/rev/de0bd7dc35e2
(In reply to Rail Aliiev [:rail] from comment #496)
> Comment on attachment 578683 [details] [diff] [review]
> Makefile hooks for signing
> 
> Transplanted to mozilla-esr10:
> http://hg.mozilla.org/releases/mozilla-esr10/rev/de0bd7dc35e2

+ bustage: https://hg.mozilla.org/releases/mozilla-esr10/rev/4ff0dc1c414b
Depends on: 733108
These constants are now listed here:

https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIWindowsRegKey#Access_constants

They're listed without explanation; I feel okay about calling this doc-complete anyway, because that's just like everything else on this page. It would be excellent if someone could update these docs to actually explain all the stuff that's not explained, but it's not something I'd be useful for.
Whiteboard: [secr:imelven], dev-doc-needed for nsIWindowsRegKey.idl → [sec-assigned:imelven], dev-doc-needed for nsIWindowsRegKey.idl
Marking sec-review-complete - unless we want to do another pass through this, in which case I suggest assigning to dveditz initially to find a reviewer.
Whiteboard: [sec-assigned:imelven], dev-doc-needed for nsIWindowsRegKey.idl → dev-doc-needed for nsIWindowsRegKey.idl
Depends on: 753775
No longer blocks: bgupdates
Blocks: 758326
doesn't work for me
Hi ntim007@yahoo.it,

Please go to about:config and set this pref to false:
app.update.stage.enabled

If you can still reproduce the problem after that please file a new bug and CC me and we can troubleshoot there.  Thanks!
Depends on: 772841
Depends on: 775458
Flags: sec-review+
No longer depends on: 794160
Depends on: 836254
No longer depends on: 836254
Depends on: CVE-2013-1706
No longer blocks: 633989
You need to log in before you can comment on or make changes to this bug.