diff --git a/GreenshotCore/Helpers/CredentialsHelper.cs b/GreenshotCore/Helpers/CredentialsHelper.cs new file mode 100644 index 000000000..256fd1cc3 --- /dev/null +++ b/GreenshotCore/Helpers/CredentialsHelper.cs @@ -0,0 +1,593 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2010 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +/// +/// The following code comes from: http://www.developerfusion.com/code/4693/using-the-credential-management-api/ +/// and is slightly modified so it works for us. +/// The following code is an example for a login, it will call the Authenticate with user/password +/// which should return true if the login worked, false if not. +/// private static bool Login(string system, string name) { +/// try { +/// CredentialsDialog dialog = new CredentialsDialog(system); +/// dialog.Name = name; +/// while (dialog.Show(dialog.Name) == DialogResult.OK) { +/// if (Authenticate(dialog.Name, dialog.Password)) { +/// if (dialog.SaveChecked) dialog.Confirm(true); +/// return true; +/// } else { +/// try { +/// dialog.Confirm(false); +/// } catch (ApplicationException) { +/// // exception handling ... +/// } +/// } +/// } +/// } catch (ApplicationException) { +/// // exception handling ... +/// } +/// return false; +/// } +/// +namespace Greenshot.Helpers { + /// Encapsulates dialog functionality from the Credential Management API. + public sealed class CredentialsDialog { + /// The only valid bitmap height (in pixels) of a user-defined banner. + private const int ValidBannerHeight = 60; + + /// The only valid bitmap width (in pixels) of a user-defined banner. + private const int ValidBannerWidth = 320; + + /// Initializes a new instance of the class + /// with the specified target. + /// The name of the target for the credentials, typically a server name. + public CredentialsDialog(string target) : this(target, null) { + } + + /// Initializes a new instance of the class + /// with the specified target and caption. + /// The name of the target for the credentials, typically a server name. + /// The caption of the dialog (null will cause a system default title to be used). + public CredentialsDialog(string target, string caption) : this(target, caption, null) { + } + + /// Initializes a new instance of the class + /// with the specified target, caption and message. + /// The name of the target for the credentials, typically a server name. + /// The caption of the dialog (null will cause a system default title to be used). + /// The message of the dialog (null will cause a system default message to be used). + public CredentialsDialog(string target, string caption, string message) : this(target, caption, message, null) { + } + + /// Initializes a new instance of the class + /// with the specified target, caption, message and banner. + /// The name of the target for the credentials, typically a server name. + /// The caption of the dialog (null will cause a system default title to be used). + /// The message of the dialog (null will cause a system default message to be used). + /// The image to display on the dialog (null will cause a system default image to be used). + public CredentialsDialog(string target, string caption, string message, Image banner) { + this.Target = target; + this.Caption = caption; + this.Message = message; + this.Banner = banner; + } + + private bool _alwaysDisplay = false; + /// + /// Gets or sets if the dialog will be shown even if the credentials + /// can be returned from an existing credential in the credential manager. + /// + public bool AlwaysDisplay { + get { + return _alwaysDisplay; + } + set { + _alwaysDisplay = value; + } + } + + private bool _excludeCertificates = true; + /// Gets or sets if the dialog is populated with name/password only. + public bool ExcludeCertificates { + get { + return _excludeCertificates; + } + set { + _excludeCertificates = value; + } + } + + private bool _persist = true; + /// Gets or sets if the credentials are to be persisted in the credential manager. + public bool Persist { + get { + return _persist; + } + set { + _persist = value; + } + } + + private bool _keepName = false; + /// Gets or sets if the name is read-only. + public bool KeepName { + get { + return _keepName; + } + set { + _keepName = value; + } + } + + private string _name = String.Empty; + /// Gets or sets the name for the credentials. + public string Name { + get { + return _name; + } + set { + if (value != null) { + if (value.Length > CREDUI.MAX_USERNAME_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The name has a maximum length of {0} characters.", + CREDUI.MAX_USERNAME_LENGTH); + throw new ArgumentException(message, "Name"); + } + } + _name = value; + } + } + + private string _password = String.Empty; + /// Gets or sets the password for the credentials. + public string Password { + get { + return _password; + } + set { + if (value != null) { + if (value.Length > CREDUI.MAX_PASSWORD_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The password has a maximum length of {0} characters.", + CREDUI.MAX_PASSWORD_LENGTH); + throw new ArgumentException(message, "Password"); + } + } + _password = value; + } + } + + private bool _saveChecked = false; + /// Gets or sets if the save checkbox status. + public bool SaveChecked { + get { + return _saveChecked; + } + set { + _saveChecked = value; + } + } + + private bool _saveDisplayed = true; + /// Gets or sets if the save checkbox is displayed. + /// This value only has effect if Persist is true. + public bool SaveDisplayed { + get { + return _saveDisplayed; + } + set { + _saveDisplayed = value; + } + } + + private string _target = String.Empty; + /// Gets or sets the name of the target for the credentials, typically a server name. + public string Target { + get { + return _target; + } + set { + if (value == null) { + throw new ArgumentException("The target cannot be a null value.", "Target"); + } else if (value.Length > CREDUI.MAX_GENERIC_TARGET_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The target has a maximum length of {0} characters.", + CREDUI.MAX_GENERIC_TARGET_LENGTH); + throw new ArgumentException(message, "Target"); + } + _target = value; + } + } + + private string _caption = String.Empty; + /// Gets or sets the caption of the dialog. + /// A null value will cause a system default caption to be used. + public string Caption { + get { + return _caption; + } + set { + if (value != null) { + if (value.Length > CREDUI.MAX_CAPTION_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The caption has a maximum length of {0} characters.", + CREDUI.MAX_CAPTION_LENGTH); + throw new ArgumentException(message, "Caption"); + } + } + _caption = value; + } + } + + private string _message = String.Empty; + /// Gets or sets the message of the dialog. + /// A null value will cause a system default message to be used. + public string Message { + get { + return _message; + } + set { + if (value != null) { + if (value.Length > CREDUI.MAX_MESSAGE_LENGTH) { + string message = String.Format( + Thread.CurrentThread.CurrentUICulture, + "The message has a maximum length of {0} characters.", + CREDUI.MAX_MESSAGE_LENGTH); + throw new ArgumentException(message, "Message"); + } + } + _message = value; + } + } + + private Image _banner = null; + /// Gets or sets the image to display on the dialog. + /// A null value will cause a system default image to be used. + public Image Banner { + get { + return _banner; + } + set { + if (value != null) { + if (value.Width != ValidBannerWidth) { + throw new ArgumentException("The banner image width must be 320 pixels.", "Banner"); + } + if (value.Height != ValidBannerHeight) { + throw new ArgumentException("The banner image height must be 60 pixels.", "Banner"); + } + } + _banner = value; + } + } + + /// Shows the credentials dialog. + /// Returns a DialogResult indicating the user action. + public DialogResult Show() { + return Show(null, this.Name, this.Password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified save checkbox status. + /// True if the save checkbox is checked. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(bool saveChecked) { + return Show(null, this.Name, this.Password, saveChecked); + } + + /// Shows the credentials dialog with the specified name. + /// The name for the credentials. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(string name) { + return Show(null, name, this.Password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified name and password. + /// The name for the credentials. + /// The password for the credentials. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(string name, string password) { + return Show(null, name, password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified name, password and save checkbox status. + /// The name for the credentials. + /// The password for the credentials. + /// True if the save checkbox is checked. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(string name, string password, bool saveChecked) { + return Show(null, name, password, saveChecked); + } + + /// Shows the credentials dialog with the specified owner. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(IWin32Window owner) { + return Show(owner, this.Name, this.Password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified owner and save checkbox status. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// True if the save checkbox is checked. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(IWin32Window owner, bool saveChecked) { + return Show(owner, this.Name, this.Password, saveChecked); + } + + /// Shows the credentials dialog with the specified owner, name and password. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// The name for the credentials. + /// The password for the credentials. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(IWin32Window owner, string name, string password) { + return Show(owner, name, password, this.SaveChecked); + } + + /// Shows the credentials dialog with the specified owner, name, password and save checkbox status. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// The name for the credentials. + /// The password for the credentials. + /// True if the save checkbox is checked. + /// Returns a DialogResult indicating the user action. + public DialogResult Show(IWin32Window owner, string name, string password, bool saveChecked) { + if ((Environment.OSVersion.Version.Major < 5) || ((Environment.OSVersion.Version.Major == 5) && (Environment.OSVersion.Version.Minor < 1))) { + throw new ApplicationException("The Credential Management API requires Windows XP / Windows Server 2003 or later."); + } + this.Name = name; + this.Password = password; + this.SaveChecked = saveChecked; + + return ShowDialog(owner); + } + + /// Confirmation action to be applied. + /// True if the credentials should be persisted. + public void Confirm(bool value) { + switch (CREDUI.ConfirmCredentials(this.Target, value)) { + case CREDUI.ReturnCodes.NO_ERROR: + break; + + case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER: + // for some reason, this is encountered when credentials are overwritten + break; + + default: + throw new ApplicationException("Credential confirmation failed."); + } + } + + /// Returns a DialogResult indicating the user action. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + /// + /// Sets the name, password and SaveChecked accessors to the state of the dialog as it was dismissed by the user. + /// + private DialogResult ShowDialog(IWin32Window owner) { + // set the api call parameters + StringBuilder name = new StringBuilder(CREDUI.MAX_USERNAME_LENGTH); + name.Append(this.Name); + + StringBuilder password = new StringBuilder(CREDUI.MAX_PASSWORD_LENGTH); + password.Append(this.Password); + + int saveChecked = Convert.ToInt32(this.SaveChecked); + + CREDUI.INFO info = GetInfo(owner); + CREDUI.FLAGS flags = GetFlags(); + + // make the api call + CREDUI.ReturnCodes code = CREDUI.PromptForCredentials( + ref info, + this.Target, + IntPtr.Zero, 0, + name, CREDUI.MAX_USERNAME_LENGTH, + password, CREDUI.MAX_PASSWORD_LENGTH, + ref saveChecked, + flags + ); + + // clean up resources + if (this.Banner != null) GDI32.DeleteObject(info.hbmBanner); + + // set the accessors from the api call parameters + this.Name = name.ToString(); + this.Password = password.ToString(); + this.SaveChecked = Convert.ToBoolean(saveChecked); + + return GetDialogResult(code); + } + + /// Returns the info structure for dialog display settings. + /// The System.Windows.Forms.IWin32Window the dialog will display in front of. + private CREDUI.INFO GetInfo(IWin32Window owner) { + CREDUI.INFO info = new CREDUI.INFO(); + if (owner != null) info.hwndParent = owner.Handle; + info.pszCaptionText = this.Caption; + info.pszMessageText = this.Message; + if (this.Banner != null) { + info.hbmBanner = new Bitmap(this.Banner, ValidBannerWidth, ValidBannerHeight).GetHbitmap(); + } + info.cbSize = Marshal.SizeOf(info); + return info; + } + + /// Returns the flags for dialog display options. + private CREDUI.FLAGS GetFlags() { + CREDUI.FLAGS flags = CREDUI.FLAGS.GENERIC_CREDENTIALS; + + if (this.AlwaysDisplay) { + flags = flags | CREDUI.FLAGS.ALWAYS_SHOW_UI; + } + + if (this.ExcludeCertificates) { + flags = flags | CREDUI.FLAGS.EXCLUDE_CERTIFICATES; + } + + if (this.Persist) { + flags = flags | CREDUI.FLAGS.EXPECT_CONFIRMATION; + if (this.SaveDisplayed) { + flags = flags | CREDUI.FLAGS.SHOW_SAVE_CHECK_BOX; + } else { + flags = flags | CREDUI.FLAGS.PERSIST; + } + } else { + flags = flags | CREDUI.FLAGS.DO_NOT_PERSIST; + } + + if (this.KeepName) { + flags = flags | CREDUI.FLAGS.KEEP_USERNAME; + } + + return flags; + } + + /// Returns a DialogResult from the specified code. + /// The credential return code. + private DialogResult GetDialogResult(CREDUI.ReturnCodes code) { + DialogResult result; + switch (code) { + case CREDUI.ReturnCodes.NO_ERROR: + result = DialogResult.OK; + break; + case CREDUI.ReturnCodes.ERROR_CANCELLED: + result = DialogResult.Cancel; + break; + case CREDUI.ReturnCodes.ERROR_NO_SUCH_LOGON_SESSION: + throw new ApplicationException("No such logon session."); + case CREDUI.ReturnCodes.ERROR_NOT_FOUND: + throw new ApplicationException("Not found."); + case CREDUI.ReturnCodes.ERROR_INVALID_ACCOUNT_NAME: + throw new ApplicationException("Invalid account name."); + case CREDUI.ReturnCodes.ERROR_INSUFFICIENT_BUFFER: + throw new ApplicationException("Insufficient buffer."); + case CREDUI.ReturnCodes.ERROR_INVALID_PARAMETER: + throw new ApplicationException("Invalid parameter."); + case CREDUI.ReturnCodes.ERROR_INVALID_FLAGS: + throw new ApplicationException("Invalid flags."); + default: + throw new ApplicationException("Unknown credential result encountered."); + } + return result; + } + } + + public sealed class GDI32 { + private GDI32() { + } + + [DllImport("gdi32.dll", EntryPoint="DeleteObject")] + public static extern bool DeleteObject(IntPtr hObject); + } + + public sealed class CREDUI { + private CREDUI() { + } + + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/authentication_constants.asp + public const int MAX_MESSAGE_LENGTH = 100; + public const int MAX_CAPTION_LENGTH = 100; + public const int MAX_GENERIC_TARGET_LENGTH = 100; + public const int MAX_DOMAIN_TARGET_LENGTH = 100; + public const int MAX_USERNAME_LENGTH = 100; + public const int MAX_PASSWORD_LENGTH = 100; + + /// + /// http://www.pinvoke.net/default.aspx/Enums.CREDUI_FLAGS + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/dpapiusercredentials.asp + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp + /// + [Flags] public enum FLAGS { + INCORRECT_PASSWORD = 0x1, + DO_NOT_PERSIST = 0x2, + REQUEST_ADMINISTRATOR = 0x4, + EXCLUDE_CERTIFICATES = 0x8, + REQUIRE_CERTIFICATE = 0x10, + SHOW_SAVE_CHECK_BOX = 0x40, + ALWAYS_SHOW_UI = 0x80, + REQUIRE_SMARTCARD = 0x100, + PASSWORD_ONLY_OK = 0x200, + VALIDATE_USERNAME = 0x400, + COMPLETE_USERNAME = 0x800, + PERSIST = 0x1000, + SERVER_CREDENTIAL = 0x4000, + EXPECT_CONFIRMATION = 0x20000, + GENERIC_CREDENTIALS = 0x40000, + USERNAME_TARGET_CREDENTIALS = 0x80000, + KEEP_USERNAME = 0x100000, + } + + /// http://www.pinvoke.net/default.aspx/Enums.CredUIReturnCodes + public enum ReturnCodes { + NO_ERROR = 0, + ERROR_INVALID_PARAMETER = 87, + ERROR_INSUFFICIENT_BUFFER = 122, + ERROR_INVALID_FLAGS = 1004, + ERROR_NOT_FOUND = 1168, + ERROR_CANCELLED = 1223, + ERROR_NO_SUCH_LOGON_SESSION = 1312, + ERROR_INVALID_ACCOUNT_NAME = 1315 + } + + /// + /// http://www.pinvoke.net/default.aspx/Structures.CREDUI_INFO + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/credui_info.asp + /// + public struct INFO { + public int cbSize; + public IntPtr hwndParent; + [MarshalAs(UnmanagedType.LPWStr)] public string pszMessageText; + [MarshalAs(UnmanagedType.LPWStr)] public string pszCaptionText; + public IntPtr hbmBanner; + } + + /// + /// http://www.pinvoke.net/default.aspx/credui.CredUIPromptForCredentialsW + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduipromptforcredentials.asp + /// + [DllImport("credui", EntryPoint="CredUIPromptForCredentialsW", CharSet=CharSet.Unicode)] + public static extern ReturnCodes PromptForCredentials( + ref INFO creditUR, + string targetName, + IntPtr reserved1, + int iError, + StringBuilder userName, + int maxUserName, + StringBuilder password, + int maxPassword, + ref int iSave, + FLAGS flags + ); + + /// + /// http://www.pinvoke.net/default.aspx/credui.CredUIConfirmCredentials + /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/creduiconfirmcredentials.asp + /// + [DllImport("credui.dll", EntryPoint="CredUIConfirmCredentialsW", CharSet=CharSet.Unicode)] + public static extern ReturnCodes ConfirmCredentials(string targetName, bool confirm); + } +} \ No newline at end of file