<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>Brite Interactive</title>
	
	<link>http://www.bryanrite.com</link>
	<description />
	<lastBuildDate>Thu, 16 Feb 2012 05:12:11 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/BryanRite" /><feedburner:info uri="bryanrite" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>Ruby on Rails WebDAV Tutorial</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/AYhGxBHvY28/</link>
		<comments>http://www.bryanrite.com/rails-webdav-tutorial/#comments</comments>
		<pubDate>Fri, 27 Jan 2012 00:54:47 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Devise]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[Tutorial]]></category>
		<category><![CDATA[WebDAV]]></category>

		<guid isPermaLink="false">http://www.bryanrite.com/?p=1081</guid>
		<description><![CDATA[Ever wanted a Devise authenticated, per-user chroot&#8217;d, WebDAV implementation for your Ruby on Rails application? Well I created one for a client and wrote a tutorial about it on Github! ...]]></description>
			<content:encoded><![CDATA[<p>Ever wanted a Devise authenticated, per-user chroot&#8217;d, WebDAV implementation for your Ruby on Rails application? Well I created one for a client and wrote a tutorial about it on Github!  Check it out:</p>
<p><a href="https://github.com/chrisroberts/dav4rack/wiki/Advanced-Rails-3-Tutorial---Custom-Resource,-Devise,-and-User-Specific-Routing">Rails 3 WebDAV Tutorial with Custom Resources, Authentication with Devise, and User Specific Routing</a></p>
<p>The great gem <a href="https://github.com/chrisroberts/dav4rack">DAV4Rack</a> and its creator Chris Roberts deserve a huge shout-out.</p>
<p><em>Note: The tutorial is part of a Wiki and is subject to change.</em></p>
<p><strong>Update: </strong>I built a sample app for this and it is available on Github: <a href="https://github.com/bryanrite/dav4rack-example-devise-subdirectories">github.com/bryanrite/dav4rack-example-devise-subdirectories</a></p>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/AYhGxBHvY28" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/rails-webdav-tutorial/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/rails-webdav-tutorial/</feedburner:origLink></item>
		<item>
		<title>Ruby on Rails CookieStore Security Concerns: Lifetime Pass</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/jI1ruKyO7tU/</link>
		<comments>http://www.bryanrite.com/ruby-on-rails-cookiestore-security-concerns-lifetime-pass/#comments</comments>
		<pubDate>Fri, 09 Sep 2011 22:38:35 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[cookie]]></category>
		<category><![CDATA[hijacking]]></category>
		<category><![CDATA[rails 3.1]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[session]]></category>
		<category><![CDATA[SSL]]></category>
		<category><![CDATA[web security]]></category>

		<guid isPermaLink="false">http://www.bryanrite.com/?p=1033</guid>
		<description><![CDATA[The CookieStore session storage in Ruby on Rails is not new; in fact, it has been the default session store since Rails 2.0. Since then, there have been countless blog ...]]></description>
			<content:encoded><![CDATA[<p>The CookieStore session storage in Ruby on Rails is not new; in fact, it has been the default session store since Rails 2.0. Since then, there have been countless blog posts and forum threads discussing various security concerns vs a server-sided store (ActiveRecordStore, Memcache, SqlStore, etc.). They all seem to miss an important point: by default, a stolen cookie gives the thief a lifetime pass to a user account!</p>
<p>I will explain how this happens and some steps you can implement to mitigate it.</p>
<p><span id="more-1033"></span></p>
<p>Most discussion seems to be focused very much on the obvious security difference: that data in the cookie is stored in plain text (well its stored in Base64, but that&#8217;s trivially convertible).  Storing sensitive information or stateful data in a session, be it server side or cookie store, is bad practice and for most of us storing simple reference information, like the logged in user&#8217;s id, this is not <em>usually</em> much of a concern.</p>
<p>Lets use the example: our cookie stores the logged in user&#8217;s ID or nothing if the user isn&#8217;t logged in. This is a common scheme used in many of the rails screencasts and guide books.</p>
<p>Instead of storing a <code>session_id</code> which identifies a server side storage record to get the user&#8217;s ID, we store the user ID in the cookie. <em>Generally</em>  this user ID isn&#8217;t sensitive, its probably in the user&#8217;s profile URL or within the code somewhere.</p>
<p>If you&#8217;re following the above rule and only storing simple reference information, your cookie isn&#8217;t much different than a server-sided store.  The HMAC digest within the Rails CookieStore cookie prevents someone from tampering and changing the cookie, so we cannot change our cookie to a different user ID and be logged in as that user, same way it would be difficult to guess a different <code>session_id</code> and access someone else&#8217;s session.</p>
<p>Since both implementations rely on a cookie being passed anyways, they have the same security concerns, and are equally susceptible to replay and fixation attacks.</p>
<h4>Ok, so what is our concern then?</h4>
<p>There is one major difference that never seems to be brought up.  If (and when!) a CookieStore cookie is stolen:</p>
<p><em>By default, a CookieStore session will never become invalid.</em></p>
<p>By this, I mean if I steal an authenticated Cookie, I can use it to access the site as that user. This is a common attack called Session Hijacking or Sidejacking but with server-sided storage it can be mitigated.</p>
<p>Generally, with a server side store, you delete the <code>session_id</code> and accompanying data when a user logs out or times out.  Any stolen <code>session_ids</code> are no longer valid because that <code>session_id</code> no longer exists.  If the valid user never logs out and the attacker keeps sending requests, they can keep the session alive, but this can still be detected and stopped.</p>
<p>With the cookie based store, even if the valid user logs out and an expiry date is put on the cookie, an attacker can change the expiry date and replay the cookie at any time.  It will always be valid as there is no <code>session_id</code> to compare against and the cookie expiry is not guarded by the HMAC digest.</p>
<p>The Rails Guides suggest using <code>reset_session</code> to stop hijacking, but this does not help for CookieStore, there is no session identifier to reset!</p>
<h4>Really?</h4>
<p>Give it a try yourself!  On any of your CookieStore based rails app: </p>
<ol>
<li>Load up Firefox and install the <a href="https://addons.mozilla.org/en-US/firefox/addon/live-http-headers/">Live HTTP Headers</a> or <a href="https://addons.mozilla.org/en-US/firefox/addon/tamper-data/">Tamper Data</a> plugin, i&#8217;ll use Live HTTP Headers.</li>
<li>Log into your app.</li>
<li>Start Live HTTP Headers</li>
<li>Go to a page that shows you if you are logged in or not.</li>
<li>On the Live HTTP Headers modal, select the main request header, its usually the top most one.  Pretend you are an attacker and read this off a wireless network.</li>
<li>Log out of your web app.</li>
<li>Back on the Live HTTP Headers modal, you are now the attacker logging in with stolen cookie: replay the main request header.</li>
<li>You will now be logged into your app.  No matter what you do, the attacker can save that stolen cookie and replay it any time from anywhere and log back in.</li>
</ol>
<h4> Uh oh! How can we stop it?</h4>
<p>Well, stopping it is quite easy, and I&#8217;ll explain a couple of ways how, but the real reason I bring this up is because it isn&#8217;t obvious to people using the default session store there is a huge concern here.  Everyone talks about how never to store sensitive information in the cookie, but getting an authenticated cookie, by default, gives you <em>life-time pass</em> to that user&#8217;s account. That seems much worse! Without a security measure, your application has a huge hole, and none of the rails tutorials, documentation, or screencasts seem to mention this.</p>
<p>No matter what authentication library you use: Devise, Authlogic, or if you roll your own, they are all susceptible because they all use whatever session store you decide.</p>
<p>The best and easiest solution is simply to use SSL.  Not just on your login forms and actions, but your <em>entire site, </em>or at least any pages where you have sessions turned on.  With SSL on, the user will not be able to replay your cookies and the entire attack vector is shut down.  Rails 3.1 has a handy <code>force_ssl</code> switch you can use, and you can use something like:</p>

<div class="wp_syntax"><div class="code"><pre class="ruby" style="font-family:monospace;"><span style="color:#ff3333; font-weight:bold;">:secure</span> <span style="color:#006600; font-weight:bold;">=&gt;</span> Rails.<span style="color:#9900CC;">env</span>.<span style="color:#9900CC;">production</span>?</pre></div></div>

<p>in your <code>config.session_store</code> declaration to ensure the cookie is only served over SSL.</p>
<p>If you don&#8217;t or can&#8217;t use SSL, try implementing a timeout and a <a href="http://en.wikipedia.org/wiki/Cryptographic_nonce">nonce </a> within the HMAC protected portion of the cookie.  </p>
<p>For example, managing a session timeout yourself by updating the expiry date on every request unless the expiry has passed creates a non-editable timeout on the session and will invalidate it after a specific time.  Of course the attacker can still touch your app to keep the timeout alive indefinitely, but it helps.</p>
<p>In addition to a timeout, adding a nonce, even something simple, can help invalidate existing cookies.  Storing a hash based on the user&#8217;s last login and/or logout time can invalidate stolen cookies every time the valid user logs in/out.  This, coupled with the timeout can mitigate hijacking and puts it on par with server-based session management schemes.</p>
<p>Regardless of the session store mechanism you use, they&#8217;re all susceptible to attack unless you&#8217;re using SSL.  Unfortunately, by default, Rails&#8217; CookieStore gives you a no-fuss lifetime pass instead of a day-pass.</p>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/jI1ruKyO7tU" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/ruby-on-rails-cookiestore-security-concerns-lifetime-pass/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/ruby-on-rails-cookiestore-security-concerns-lifetime-pass/</feedburner:origLink></item>
		<item>
		<title>Preventing Brute Force Attacks on your Web Login</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/5ci1NHnZ0IY/</link>
		<comments>http://www.bryanrite.com/preventing-brute-force-attacks-on-your-web-login/#comments</comments>
		<pubDate>Fri, 03 Jun 2011 01:23:43 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[brute force]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[captcha]]></category>
		<category><![CDATA[dictionary attacks]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[login]]></category>
		<category><![CDATA[password cracking]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[web applications]]></category>
		<category><![CDATA[web security]]></category>

		<guid isPermaLink="false">http://www.bryanrite.com/?p=976</guid>
		<description><![CDATA[Preventing Brute Force and Dictionary Attacks can be a tricky thing &#8211; especially without taking great lengths or causing valid users painful hoops to jump through.  There are lots of ...]]></description>
			<content:encoded><![CDATA[<p>Preventing <a title="Brute Force Attacks" href="http://en.wikipedia.org/wiki/Brute-force_attack">Brute Force</a> and <a title="Dictionary Attacks" href="http://en.wikipedia.org/wiki/Dictionary_attack">Dictionary Attacks</a> can be a tricky thing &#8211; especially without taking great lengths or causing valid users painful hoops to jump through.  There are lots of articles out there explaining ways to stop brute force attacks but a lot of these make the same inaccurate assumptions about your attacker.</p>
<p>I will discuss some <em>realistic </em>ways of preventing brute force attacks, taking a valid user&#8217;s experience into account, explain why a lot of tutorials out there on brute force prevention are not effective, and a solution to the problem.</p>
<p><span id="more-976"></span></p>
<h4>Why Do I Need Brute Force Protection?</h4>
<p>Actually, hopefully you don&#8217;t.  There are great services out there that you can outsource your authentication to like <a title="OpenID" href="http://openid.net/">OpenID</a>, <a title="Facebook Connect" href="http://developers.facebook.com/docs/guides/web/#login">Facebook Connect</a>, and <a title="Google Authentication" href="http://code.google.com/apis/accounts/docs/OpenID.html">Google Auth</a> (OpenID).  The same way you probably outsource your comment spam protection, these services have more resources tasked to prevent unauthorized access to accounts than you can bring to bare.  In addition, some of these services offer a form of two-factor authentication to their users, usually in the form of an SMS message, making the task of brute forcing an account very difficult if not impossible.</p>
<p>If you are unable or unwilling to integrate an external authentication service, and its pretty common to offer them only as an option anyways, then you will need a brute force solution.  A typical username/password authentication system, especially with public user registration, is very susceptible and most guides you read typically only talk about preventing SQL Injection.</p>
<h4>Why Are Most Other Tutorials Wrong?</h4>
<p>Most sites and forums suggestions suffer from two major design flaws: they punish valid users (CAPTCHAs, account locking) and they expect the attacker to act like a regular user (Session or IP tracking).</p>
<p>Turing tests are a usability problem.  They can generally have a high defeat rate, sometimes in the range of 90%, and can greatly lower your site&#8217;s conversion rate.  They punish real users and don&#8217;t stop automated attempts significantly enough.</p>
<p>Account locking (X failed login attempts locks your account for an hour) opens the avenue for a DoS attack on your website. Continuous brute force attempts cause accounts to be locked, locking accounts generally produces a different error message, so when an account gets locked the attacker can start to map valid usernames, further making it easier to lock them out.</p>
<p>Another incorrect assumption is that you can use Cookies, Sessions, or IPs to prevent brute force.  Sessions are based on a cookie or a URL parameter, and if you&#8217;re attempting to brute force a login, you aren&#8217;t going to allow the target to track your attempts by helping them save your state: Session&#8217;s can&#8217;t be trusted.  IP tracking is not going to help an attack as you can spoof or use proxies to change your IP.  Also, a large number of users behind a particular proxy or NAT&#8217;d router could give you a false-positive: IPs can&#8217;t be trusted.</p>
<h4>So How Are We Going to Do This?  Punish the Machines.</h4>
<p>Brute force works by trying all permutations until it stumbles across the correct one.  Generally this would be done by a machine because the key space of a reasonable password policy is too large to do manually.  We can exploit this man vs. machine to make sure our valid users are kept happy while punishing machines.</p>
<p>The faster password attempts can be made, the faster the password will be found, so lets exploit this by introducing 3 pieces of entropy: speed, scope, and complexity.  We can introduce each of these items in a way that does not punish valid users with the assumption: <em>A valid user will not try their password X times without starting to experience unwanted effects.</em> Several permutations is insignificant for a brute force attempt, but likely a lot for a valid user, and we&#8217;ll bring the onset of unwanted effects slowly, to help steer the user to the recover password system.</p>
<p><strong>Speed:</strong> by increasing the response time of our authentication request, it takes longer for each attempt, which takes longer to find the correct password.  At a certain number of attempts we can determine that it is likely not a valid user and begin to increase the response time artificially to a point where each attempt takes a maximum amount of time making the entire permutation unreasonable.</p>
<p><strong>Scope:</strong> We can greatly increase the size of the problem of a brute force attempt by having the same response, regardless if the username is a valid one or not:  the attacker doesn&#8217;t know if its attacking a real account or just wasting time.  Any error messages and response times should be the same.</p>
<p><strong>Complexity:</strong> Increasing the complexity of each attempt simply makes it more difficult, causing the failure count to go up, which will increase the time per request.  By introducing a Turing test at a certain time, like after 10 failed attempts, we are likely to avoid the valid user and just punish the machine, changing the form and introducing a third problem to solve.</p>
<h4>Putting It All Together</h4>
<p>Below is a example pseudo-code-ish implementation using reCAPTCHA as the Turing test.  This should be easily ported to C#, PHP, Java, or Ruby.</p>

<div class="wp_syntax"><div class="code"><pre class="csharp" style="font-family:monospace;"><span style="color: #0600FF; font-weight: bold;">protected</span> <span style="color: #6666cc; font-weight: bold;">void</span> Login<span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #6666cc; font-weight: bold;">string</span> email <span style="color: #008000;">=</span> <span style="color: #0600FF; font-weight: bold;">this</span><span style="color: #008000;">.</span><span style="color: #0000FF;">textboxEmail</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Text</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Trim</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">.</span><span style="color: #0000FF;">ToLower</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
    <span style="color: #6666cc; font-weight: bold;">string</span> password <span style="color: #008000;">=</span> <span style="color: #0600FF; font-weight: bold;">this</span><span style="color: #008000;">.</span><span style="color: #0000FF;">textboxPassword</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Text</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Trim</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
&nbsp;
    <span style="color: #0600FF; font-weight: bold;">if</span> <span style="color: #008000;">&#40;</span><span style="color: #6666cc; font-weight: bold;">String</span><span style="color: #008000;">.</span><span style="color: #0000FF;">IsNullOrEmpty</span><span style="color: #008000;">&#40;</span>email<span style="color: #008000;">&#41;</span> <span style="color: #008000;">||</span> <span style="color: #6666cc; font-weight: bold;">String</span><span style="color: #008000;">.</span><span style="color: #0000FF;">IsNullOrEmpty</span><span style="color: #008000;">&#40;</span>password<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        <span style="color: #008080; font-style: italic;">// TODO: Display &quot;form is incomplete&quot; error message.</span>
        <span style="color: #0600FF; font-weight: bold;">return</span><span style="color: #008000;">;</span>
    <span style="color: #008000;">&#125;</span>
&nbsp;
    User user <span style="color: #008000;">=</span> UserFactory<span style="color: #008000;">.</span><span style="color: #0000FF;">ByEmail</span><span style="color: #008000;">&#40;</span>email<span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
    <span style="color: #008080; font-style: italic;">// We have a Database Table or Cache of keys and counts; in this</span>
    <span style="color: #008080; font-style: italic;">// case: email address and failed attempt count. Since we track</span>
    <span style="color: #008080; font-style: italic;">// failures whether the user account exists or not, we store this</span>
    <span style="color: #008080; font-style: italic;">// value separately from the user.</span>
    <span style="color: #6666cc; font-weight: bold;">int</span> failedAttempts <span style="color: #008000;">=</span> UserFactory<span style="color: #008000;">.</span><span style="color: #0000FF;">GetFailedLoginCount</span><span style="color: #008000;">&#40;</span>email<span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
&nbsp;
    <span style="color: #008080; font-style: italic;">// Run our Brute Force Proection, regardless if the user is valid.</span>
    <span style="color: #6666cc; font-weight: bold;">bool</span> captchaRequired <span style="color: #008000;">=</span> BruteForceProtection<span style="color: #008000;">&#40;</span>email, failedAttempts<span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
&nbsp;
    <span style="color: #008080; font-style: italic;">// Check that the user submitted and passed a captcha. If they</span>
    <span style="color: #008080; font-style: italic;">// didn't redirect them back to the login using a URL parameter</span>
    <span style="color: #008080; font-style: italic;">// to indicate to show the captcha, and increment the counter.</span>
    <span style="color: #0600FF; font-weight: bold;">if</span> <span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span>captchaRequired<span style="color: #008000;">&#41;</span> <span style="color: #008000;">&amp;&amp;</span> <span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">!</span>Captcha_Submitted<span style="color: #008000;">&#41;</span> <span style="color: #008000;">||</span> <span style="color: #008000;">&#40;</span><span style="color: #008000;">!</span>Captcha_Passed<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        UserFactory<span style="color: #008000;">.</span><span style="color: #0000FF;">IncrementFailedLoginCount</span><span style="color: #008000;">&#40;</span>email<span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
        <span style="color: #008080; font-style: italic;">// TODO: Display &quot;captcha failed&quot; error message.</span>
        Redirect<span style="color: #008000;">&#40;</span><span style="color: #666666;">&quot;/LoginPage/?captcha=1&quot;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
    <span style="color: #008000;">&#125;</span>
&nbsp;
    <span style="color: #008080; font-style: italic;">// The user doesn't exist or the passwords don't match.</span>
    <span style="color: #0600FF; font-weight: bold;">if</span> <span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span>user <span style="color: #008000;">==</span> <span style="color: #0600FF; font-weight: bold;">null</span><span style="color: #008000;">&#41;</span> <span style="color: #008000;">||</span> <span style="color: #008000;">&#40;</span>user<span style="color: #008000;">.</span><span style="color: #0000FF;">Password</span> <span style="color: #008000;">!=</span> User<span style="color: #008000;">.</span><span style="color: #0000FF;">EncryptPass</span><span style="color: #008000;">&#40;</span>password<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        UserFactory<span style="color: #008000;">.</span><span style="color: #0000FF;">IncrementFailedLoginCount</span><span style="color: #008000;">&#40;</span>email<span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
        <span style="color: #008080; font-style: italic;">// TODO: Display &quot;invalid login&quot; error message.</span>
        <span style="color: #0600FF; font-weight: bold;">return</span><span style="color: #008000;">;</span>
    <span style="color: #008000;">&#125;</span>
&nbsp;
    <span style="color: #008080; font-style: italic;">// Account is authenticated successfully.</span>
&nbsp;
    <span style="color: #008080; font-style: italic;">// Clear failed attempts count.</span>
    UserFactory<span style="color: #008000;">.</span><span style="color: #0000FF;">ClearFailedLogins</span><span style="color: #008000;">&#40;</span>email<span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
&nbsp;
    <span style="color: #008080; font-style: italic;">// Log the user as you normally would:</span>
    Authenticate<span style="color: #008000;">.</span><span style="color: #0000FF;">Login</span><span style="color: #008000;">&#40;</span>user<span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
&nbsp;
    Redirect<span style="color: #008000;">&#40;</span><span style="color: #666666;">&quot;/Profile/&quot;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>


<div class="wp_syntax"><div class="code"><pre class="csharp" style="font-family:monospace;"><span style="color: #0600FF; font-weight: bold;">private</span> <span style="color: #6666cc; font-weight: bold;">bool</span> BruteForceProtection<span style="color: #008000;">&#40;</span><span style="color: #6666cc; font-weight: bold;">string</span> email, <span style="color: #6666cc; font-weight: bold;">int</span> failedCount<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #008080; font-style: italic;">// The metrics used are somewhat arbitrary, but seem to work well.</span>
    <span style="color: #0600FF; font-weight: bold;">if</span> <span style="color: #008000;">&#40;</span>failedCount <span style="color: #008000;">&gt;=</span> <span style="color: #FF0000;">30</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        <span style="color: #008080; font-style: italic;">// At this point we are pretty positive its a brute force attempt:</span>
&nbsp;
        <span style="color: #008080; font-style: italic;">// At the 30th failure, we send an email to support about the brute force attempt.</span>
        <span style="color: #0600FF; font-weight: bold;">if</span> <span style="color: #008000;">&#40;</span>failedCount <span style="color: #008000;">==</span> <span style="color: #FF0000;">30</span><span style="color: #008000;">&#41;</span>
            Email<span style="color: #008000;">.</span><span style="color: #0000FF;">SendEmail</span><span style="color: #008000;">&#40;</span><span style="color: #666666;">&quot;Brute Force Attempt Detected.&quot;</span>,
            <span style="color: #6666cc; font-weight: bold;">String</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Format</span><span style="color: #008000;">&#40;</span><span style="color: #666666;">&quot;Attempted brute force of email: {0} from IP: {1}&quot;</span>, email, RemoteIP<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
        <span style="color: #008080; font-style: italic;">// Sleep between 30 seconds and 1 min per request</span>
        Thread<span style="color: #008000;">.</span><span style="color: #0000FF;">Sleep</span><span style="color: #008000;">&#40;</span>Math<span style="color: #008000;">.</span><span style="color: #0000FF;">Max</span><span style="color: #008000;">&#40;</span>failedCount <span style="color: #008000;">*</span> <span style="color: #FF0000;">1000</span>, <span style="color: #FF0000;">60000</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
        <span style="color: #008080; font-style: italic;">// Enable Captcha</span>
        <span style="color: #0600FF; font-weight: bold;">return</span> <span style="color: #0600FF; font-weight: bold;">true</span><span style="color: #008000;">;</span>
    <span style="color: #008000;">&#125;</span>
    <span style="color: #0600FF; font-weight: bold;">else</span> <span style="color: #0600FF; font-weight: bold;">if</span> <span style="color: #008000;">&#40;</span>failedCount <span style="color: #008000;">&gt;=</span> <span style="color: #FF0000;">15</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        <span style="color: #008080; font-style: italic;">// Sleep between ~9 and ~21 seconds</span>
        Thread<span style="color: #008000;">.</span><span style="color: #0000FF;">Sleep</span><span style="color: #008000;">&#40;</span>failedCount <span style="color: #008000;">*</span> <span style="color: #FF0000;">600</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
        <span style="color: #008080; font-style: italic;">// Enable Captcha</span>
        <span style="color: #0600FF; font-weight: bold;">return</span> <span style="color: #0600FF; font-weight: bold;">true</span><span style="color: #008000;">;</span>
    <span style="color: #008000;">&#125;</span>
    <span style="color: #0600FF; font-weight: bold;">else</span> <span style="color: #0600FF; font-weight: bold;">if</span> <span style="color: #008000;">&#40;</span>failedCount <span style="color: #008000;">&gt;=</span> <span style="color: #FF0000;">3</span><span style="color: #008000;">&#41;</span>
    <span style="color: #008000;">&#123;</span>
        <span style="color: #008080; font-style: italic;">// Sleep between 1.5 seconds and 7 seconds.</span>
        Thread<span style="color: #008000;">.</span><span style="color: #0000FF;">Sleep</span><span style="color: #008000;">&#40;</span>failedCount <span style="color: #008000;">*</span> <span style="color: #FF0000;">500</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
        <span style="color: #008080; font-style: italic;">// No Captcha</span>
        <span style="color: #0600FF; font-weight: bold;">return</span> <span style="color: #0600FF; font-weight: bold;">false</span><span style="color: #008000;">;</span>
    <span style="color: #008000;">&#125;</span>
&nbsp;
    <span style="color: #0600FF; font-weight: bold;">return</span> <span style="color: #0600FF; font-weight: bold;">false</span><span style="color: #008000;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<p>As you can see, as the number of failed attempts increase, the time and complexity increase.  At a certain point, we are notified of the brute force, and the entropy increases to the point where each request takes long enough that brute force becomes an unreasonable.  We are also notified, via email in this case, of the offending IPs and at risk usernames, so we have ample time to do something about it.</p>
<h4>Problems to Watch For:</h4>
<ul>
<li><strong>Horizontal Brute Force</strong> &#8211; We&#8217;ve looked at brute force attempts vertically, attacking a username, but an attacker could use a common password and attack the system horizontally, trying different usernames.  You can simply add hashed password to the key table from the above solution, storing a count for each failed password attempt and lock them down that way.</li>
<li><strong>Password Recovery</strong>: Your authentication system might be top notch, but often password recovery functionality is not.  Ensure you don&#8217;t give away valid usernames and that your recovery system is well implemented.</li>
<li><strong>Account Locking</strong>: We don&#8217;t actually &#8220;Lock&#8221; accounts, but we do implement a large enough timeout to make it uncomfortable for real users if a DoS attack is attempted &#8211; requiring them to wait when logging in.  The email sent above should help mitigate DoS attacks, and its much better than actually locking the account.  You could potentially lower the max second wait time, or decrement the failed login attempts after a specific time frame, so over time it goes back to zero.</li>
<li><strong>Registration </strong>- If an account that doesn&#8217;t exist is attempted to be brute forced and that account is later registered by a valid user, the initial login time for our new user could be a long time, so perhaps clear the failed login count on newly registered accounts.</li>
</ul>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/5ci1NHnZ0IY" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/preventing-brute-force-attacks-on-your-web-login/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/preventing-brute-force-attacks-on-your-web-login/</feedburner:origLink></item>
		<item>
		<title>Internationalization Strategies in ASP.Net (and lessons for other languages)</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/BltBiOcNaNY/</link>
		<comments>http://www.bryanrite.com/internationalization-strategies-in-asp-net-and-lessons-for-other-languages/#comments</comments>
		<pubDate>Fri, 15 Apr 2011 20:22:17 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[asp.net]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[culture]]></category>
		<category><![CDATA[globalization]]></category>
		<category><![CDATA[internationalization]]></category>
		<category><![CDATA[language]]></category>
		<category><![CDATA[localization]]></category>

		<guid isPermaLink="false">http://www.bryanrite.com/?p=914</guid>
		<description><![CDATA[Internationalization can be a tough area to do properly, in a scalable and manageable way. Most languages have their own system for handling different languages and cultures: Ruby uses its ...]]></description>
			<content:encoded><![CDATA[<p>Internationalization can be a tough area to do properly, in a scalable and manageable way. Most languages have their own system for handling different languages and cultures: Ruby uses its i10n library and YML files, PHP uses GNU gettext and PO files, while ASP.Net uses XML files presented by the IDE in a convenient way called Resource files.</p>
<p>I will explain some useful tips we learned while implementing Internationalization in our <a title="Cashless Schools" href="http://www.bryanrite.com/portfolio/cashless-schools/">Cashless Schools</a> project.</p>
<p><span id="more-914"></span></p>
<h4>Understanding Globalization and Localization</h4>
<p>Internationalization can be roughly broken down into two sections: Globalization and Localization, and understanding the difference between these is important.</p>
<p>When we talk about internationalization, people mainly think about multiple languages.  For example, we want to offer our project in English and Chinese.  What we&#8217;re really saying is we want to localize our project with two different languages.  Essentially replacing the text depending on the user&#8217;s preference.</p>
<p>Now say that project is selling a product&#8230; are we going to change the currency symbols to match the language?  Are all your prices going to be converted to Yuan for your Chinese speaking users?  In some cases, yes; in a lot of cases, no, we still want to display currency and date formats in a single culture.  This is the difference between Localization, language, and Globalization, culture.</p>
<p>Take for instance the Apple Store.  The Apple Store is Globalized for several different cultures.  When you go to the Chinese store, it will be selling its products to people in China, using the Chinese language and culture (zh-CN) is the right thing to do.  Now say they offered a version of its USA store in Mexican-Spanish.  Apple would likely not display currency in Pesos, it is still the USA store, they just want to make it more friendly to native Spanish speakers.  In this case we would use the US culture (en-US) and Mexican-Spanish language (es-MX): the text would be displayed in Spanish, but the currency symbols would continue to be dollars.</p>
<p>ASP.Net allows us to specify these settings separately:</p>

<div class="wp_syntax"><div class="code"><pre class="csharp" style="font-family:monospace;">Thread<span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentThread</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentCulture</span> <span style="color: #008000;">=</span> CultureInfo<span style="color: #008000;">.</span><span style="color: #0000FF;">CreateSpecificCulture</span><span style="color: #008000;">&#40;</span><span style="color: #666666;">&quot;en-US&quot;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
Thread<span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentThread</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentUICulture</span> <span style="color: #008000;">=</span> CultureInfo<span style="color: #008000;">.</span><span style="color: #0000FF;">CreateSpecificCulture</span><span style="color: #008000;">&#40;</span><span style="color: #666666;">&quot;es-MX&quot;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span></pre></div></div>

<h4>What&#8217;s so important about the Culture</h4>
<p>In my experience, cultural settings are most important in two areas: currency and date format.</p>
<p>Currency can have different currency symbols ($, €, ¥), different thousands separators, different decimal separators.  Dates can be displayed in different formats (m/d/y, d/m/y).</p>
<p>Since these can differ from our localization settings, we need a way to dynamically show them based on the culture (not the language).  ASP.Net has the handy <code>.ToString()</code> method which can take standard identifiers like &#8220;C&#8221; or &#8220;D&#8221;.  ASP.Net will automatically output the values based on your current culture, but when you want to customize the output, check out the following libraries:</p>

<div class="wp_syntax"><div class="code"><pre class="csharp" style="font-family:monospace;"><span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">CultureInfo</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentCulture</span><span style="color: #008000;">.</span><span style="color: #0000FF;">NumberFormat</span>
<span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">CultureInfo</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentCulture</span><span style="color: #008000;">.</span><span style="color: #0000FF;">DateTimeFormat</span></pre></div></div>

<p>They will have everything you need, from the current cultures date format strings to the currency symbol.  Below are a couple of Extensions I commonly use to display data.  As you can see they take the format from the culture and the language from the ui culture.</p>

<div class="wp_syntax"><div class="code"><pre class="csharp" style="font-family:monospace;"><span style="color: #0600FF; font-weight: bold;">public</span> <span style="color: #0600FF; font-weight: bold;">static</span> <span style="color: #6666cc; font-weight: bold;">string</span> ToLongDateNoWeekDayString<span style="color: #008000;">&#40;</span><span style="color: #0600FF; font-weight: bold;">this</span> DateTime date, <span style="color: #6666cc; font-weight: bold;">bool</span> abbrieviate<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #6666cc; font-weight: bold;">string</span> dateFormat <span style="color: #008000;">=</span> <span style="color: #000000;">System.<span style="color: #0000FF;">Text</span><span style="color: #008000;">.</span><span style="color: #0000FF;">RegularExpressions</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">Regex</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Replace</span><span style="color: #008000;">&#40;</span><span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">CultureInfo</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentCulture</span><span style="color: #008000;">.</span><span style="color: #0000FF;">DateTimeFormat</span><span style="color: #008000;">.</span><span style="color: #0000FF;">LongDatePattern</span>, <span style="color: #666666;">@&quot;,?\s*dddd?,?&quot;</span>, <span style="color: #666666;">&quot;&quot;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
&nbsp;
    <span style="color: #0600FF; font-weight: bold;">if</span> <span style="color: #008000;">&#40;</span>abbrieviate<span style="color: #008000;">&#41;</span>
        <span style="color: #0600FF; font-weight: bold;">return</span> date<span style="color: #008000;">.</span><span style="color: #0000FF;">ToString</span><span style="color: #008000;">&#40;</span><span style="color: #000000;">System.<span style="color: #0000FF;">Text</span><span style="color: #008000;">.</span><span style="color: #0000FF;">RegularExpressions</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">Regex</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Replace</span><span style="color: #008000;">&#40;</span>dateFormat, <span style="color: #666666;">@&quot;MMMM&quot;</span>, <span style="color: #666666;">&quot;MMM&quot;</span><span style="color: #008000;">&#41;</span>, <span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">CultureInfo</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentUICulture</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
    <span style="color: #0600FF; font-weight: bold;">else</span>
        <span style="color: #0600FF; font-weight: bold;">return</span> date<span style="color: #008000;">.</span><span style="color: #0000FF;">ToString</span><span style="color: #008000;">&#40;</span>dateFormat, <span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">CultureInfo</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentUICulture</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
<span style="color: #008000;">&#125;</span>
&nbsp;
<span style="color: #0600FF; font-weight: bold;">public</span> <span style="color: #0600FF; font-weight: bold;">static</span> <span style="color: #6666cc; font-weight: bold;">string</span> ToLongDateTimeNoWeekDayString<span style="color: #008000;">&#40;</span><span style="color: #0600FF; font-weight: bold;">this</span> DateTime date, <span style="color: #6666cc; font-weight: bold;">bool</span> removeSeconds<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #6666cc; font-weight: bold;">string</span> pattern <span style="color: #008000;">=</span> <span style="color: #008000;">&#40;</span>removeSeconds<span style="color: #008000;">&#41;</span> <span style="color: #008000;">?</span> <span style="color: #666666;">@&quot;(,?\s*dddd?,?)|(:ss)&quot;</span> <span style="color: #008000;">:</span> <span style="color: #666666;">@&quot;,?\s*dddd?,?&quot;</span><span style="color: #008000;">;</span>
    <span style="color: #0600FF; font-weight: bold;">return</span> date<span style="color: #008000;">.</span><span style="color: #0000FF;">ToString</span><span style="color: #008000;">&#40;</span><span style="color: #000000;">System.<span style="color: #0000FF;">Text</span><span style="color: #008000;">.</span><span style="color: #0000FF;">RegularExpressions</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">Regex</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Replace</span><span style="color: #008000;">&#40;</span><span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">CultureInfo</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentCulture</span><span style="color: #008000;">.</span><span style="color: #0000FF;">DateTimeFormat</span><span style="color: #008000;">.</span><span style="color: #0000FF;">FullDateTimePattern</span>, pattern, <span style="color: #666666;">&quot;&quot;</span><span style="color: #008000;">&#41;</span>, <span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">CultureInfo</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentUICulture</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
<span style="color: #008000;">&#125;</span>
&nbsp;
<span style="color: #0600FF; font-weight: bold;">public</span> <span style="color: #0600FF; font-weight: bold;">static</span> <span style="color: #6666cc; font-weight: bold;">string</span> ToCulturalCurrencyWithoutSign<span style="color: #008000;">&#40;</span><span style="color: #0600FF; font-weight: bold;">this</span> <span style="color: #6666cc; font-weight: bold;">decimal</span> amount<span style="color: #008000;">&#41;</span>
<span style="color: #008000;">&#123;</span>
    <span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">NumberFormatInfo</span> ni <span style="color: #008000;">=</span> <span style="color: #008000;">&#40;</span><span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">NumberFormatInfo</span><span style="color: #008000;">&#41;</span><span style="color: #000000;">System.<span style="color: #0000FF;">Globalization</span></span><span style="color: #008000;">.</span><span style="color: #0000FF;">CultureInfo</span><span style="color: #008000;">.</span><span style="color: #0000FF;">CurrentCulture</span><span style="color: #008000;">.</span><span style="color: #0000FF;">NumberFormat</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Clone</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
    ni<span style="color: #008000;">.</span><span style="color: #0000FF;">CurrencySymbol</span> <span style="color: #008000;">=</span> <span style="color: #666666;">&quot;&quot;</span><span style="color: #008000;">;</span>
    <span style="color: #0600FF; font-weight: bold;">return</span> amount<span style="color: #008000;">.</span><span style="color: #0000FF;">ToString</span><span style="color: #008000;">&#40;</span><span style="color: #666666;">&quot;C&quot;</span>, ni<span style="color: #008000;">&#41;</span><span style="color: #008000;">.</span><span style="color: #0000FF;">Trim</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#41;</span><span style="color: #008000;">;</span>
<span style="color: #008000;">&#125;</span></pre></div></div>

<h4>Regional Dialects</h4>
<p>With the culture, we always have to specify a region.  For example, our site&#8217;s culture cannot be English (en), it has to be USA English (en-us), Canadian English (en-ca), UK English (en-gb), etc. Because all of these have slightly different settings, there is no &#8220;English&#8221; culture.</p>
<p>This is not true with language.  We can create generalized resource files as well as specify regional or dialect specific files.  ASP.Net will automatically select the best resource file to use.  For example, we can create resource files for general english and override it with regional specific one.  All we have to do is name the resource files properly:</p>
<pre>App_LocalResources
- Example.aspx.en.resx
- Example.aspx.en-CA.resx
- Example.aspx.es.resx
- Example.aspx.es-DO.resx
- Example.aspx.es-MX.resx</pre>
<p>Above we have 5 different resource files:</p>
<ul>
<li>en : General English.</li>
<li>en-CA : Canadian English (we have more u&#8217;s!)</li>
<li>es : Spanish</li>
<li>es-DO : Dominican Republic Spanish</li>
<li>es-MX : Mexican Spanish</li>
</ul>
<p>ASP.Net will select the most specific file based on your UI Culture.</p>
<h4>Localization Files and Variables</h4>
<p>Another important feature of localization is the use of variables in our localization strings.  Languages can be very complex and it is important to remember you cannot just break up a string around a variable.</p>
<p>For a very simple example, &#8220;You just added <em>War and Peace</em> to your Shopping Cart.&#8221;, is a common type of string you&#8217;ll need.  The variable <em>War and Peace</em> can be replaced with any item you are adding to your shopping cart.  You may be tempted to localize the string as:</p>
<ol>
<li><code>You just added</code></li>
<li><code>to your Shopping Cart.</code></li>
</ol>
<p>And output it around a variable on your page.  Since in different languages, the structure of the sentence can vary greatly, it is highly recommended to use String.Format and numbered variables.  This allows the localized versions of this sentence all the flexibility they need.  You would want to localize the string like:</p>
<p><code>You just added {0} to your Shopping Cart.</code></p>
<p>And display it with:</p>
<p><code>String.Format(GetLocalResourceObject("example").ToString(), item.Name)</code></p>
<p>The same idea goes for logical paragraphs or basic formatting.  Instead of breaking a paragraph up into multiple separate sentences, always try to keep logically grouped text together as much as possible, and I personally have no problems allowing basic HTML formatting in localized strings.  This allows for smooth translations, as the more the text is broken up, the choppier the translations will be.</p>
<h4>Plurals</h4>
<p>The use of plurals is an important and difficult syntax to master.  In English, and some other languages, we only worry about 2 cases, maybe 3:</p>
<ul>
<li>You have <em>multiple </em>items.</li>
<li>You have <em>a single</em> item.</li>
<li>You have <em>zero</em> items.  (usually the same as multiple, but in some cases it can be awkward)</li>
</ul>
<p>This can be managed by storing the 2-3 different strings in our resource files and extending GetLocalResourceObject to include an identifier for which string to use based on the count.</p>
<p>In many languages, it can be more complex then that.  In Polish for example, the grammar for 1, 2-4, and 0 or 4+ is different, and changes again after 20.  GNU&#8217;s gettext has a <a href="http://www.gnu.org/s/hello/manual/gettext/Plural-forms.html">pretty good solution</a> to this dynamic type of pluralization, but ASP.Net does not, you will have to come up with your own solution if that need arises in your project.</p>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/BltBiOcNaNY" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/internationalization-strategies-in-asp-net-and-lessons-for-other-languages/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/internationalization-strategies-in-asp-net-and-lessons-for-other-languages/</feedburner:origLink></item>
		<item>
		<title>Remote Incremental Backups via Rsync and SSH to a Drobo</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/KqYpcd77Aqo/</link>
		<comments>http://www.bryanrite.com/drobo-incremental-rsync-backups/#comments</comments>
		<pubDate>Wed, 30 Mar 2011 20:25:43 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[automated]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[drobo]]></category>
		<category><![CDATA[hard-linking]]></category>
		<category><![CDATA[incremental]]></category>
		<category><![CDATA[passwordless]]></category>
		<category><![CDATA[rsync]]></category>
		<category><![CDATA[scripting]]></category>
		<category><![CDATA[ssh]]></category>

		<guid isPermaLink="false">http://www.bryanrite.com/?p=842</guid>
		<description><![CDATA[A client had a catastrophic fileserver failure resulting in the loss of a significant amount of important data.  As a result,  I was called in to setup an automated offsite ...]]></description>
			<content:encoded><![CDATA[<p>A client had a catastrophic fileserver failure resulting in the loss of a significant amount of important data.  As a result,  I was called in to setup an automated offsite backup.  Due to many factors, I decided to implement a DroboFS instead of a hosted cloud or regular *nix fileserver.</p>
<p>Using the Drobo, there were a couple of gotchas.  It doesn&#8217;t have sshd enabled, no bash shell, no rsync (or rsnapshot which would make this easier) and more.</p>
<p>I will describe how I setup an automated, passwordless, incremental differential, hard-linked backup using rsync across ssh.</p>
<p><span id="more-842"></span></p>
<h4>Whats an incremental differential hard-linked backup?</h4>
<p>It is essentially the same as Apple&#8217;s Time Machine system.  We create a backup using only the files and directories that have changed, but still store it as a full backup on the backup media.  In other words, every time we backup, we will only transfer what has changed, but if you look at that backup on the remote computer, all the files will be there.</p>
<p>How do we do this?  Utilizing hard-linking in the filesystem.  Essentially we are creating two &#8220;pointers&#8221; that reference the same file (inode).  That way each backup folder has its own copy of the file, but we only store the file once.  If you run the command <code>stat</code> on a file or directory, you can see in the output how many links to that inode there are, or run <code>ls -i</code> to see the inode you are pointing to.  New versions of that file won&#8217;t overwrite the old ones, the new backups will reference a different inode, keeping your historical data.</p>
<p>This helps for several reasons.  When we need to restore the entire filesystem, we can just move all the files from one directory, rather than merging the incrementals into a delta and applying it to the full backup to get our latest system and we only transfer what has changed which dramatically reduces bandwidth, time, and space.</p>
<h4>Let&#8217;s get started&#8230;</h4>
<p>First things first, setup your Drobo as the instructions specify and be sure to enable <a href="http://support.datarobotics.com/ci/fattach/get/25295/1286306491/redirect/1/session/L2F2LzEvc2lkL0lReUZRZXFr">DroboApps</a>.  Create a share on the Drobo for your backups.  I&#8217;ll name mine <code>backup</code>, if you use a different name, edit the below scripts as necessary.</p>
<p>You will then need to install the <a href="http://www.drobo.com/droboapps/apps-for-drobofs.php#52">Dropbear SSH</a> and <a href="http://www.drobo.com/droboapps/apps-for-drobofs.php#58">Rsync</a> apps for the following to work.  [<em>Note</em>: You do not need the Apache or Admin apps, but you might find them useful.]</p>
<p>At this point you should be able to login via SSH to your Drobo using the default SSH credentials, at the time of writing for the DroboFS its root and root.  We&#8217;ll update the root SSH password using the following command:</p>
<pre># /mnt/DroboFS/Shares/DroboApps/dropbear/root_passwd</pre>
<p>This will reset and persist our root password between reboots.</p>
<p>[<em>Note</em>: I know that allowing root SSH access is not ideal but I have so far been unable to stop the drobo from allowing it if SSH is enabled... perhaps a commenter will have a solution?]</p>
<p>Now we need to edit the rsync config file:</p>
<pre># vi /mnt/DroboFS/Shares/DroboApps/rsync/rsyncd.conf</pre>
<p>You should see one share named [drobofs].  We want to replace it so that the file looks like:</p>
<pre>uid = root
gid = root
pid file = /mnt/DroboFS/Shares/DroboApps/rsync/rsyncd.pid

[drobofs]
        path = /mnt/DroboFS/Shares/backup
        comment = Backup Share
        read only = false</pre>
<p>[<em>Note:</em> if you named your backup share different, you'll need to specify it here.]</p>
<p>Alright, now to provide passwordless login.  From the machine that has the data you want backed up, generate your SSH keys.  You will likely not want to use a passphrase and I&#8217;ll leave you to secure SSH as you see fit (perhaps read up about <em>forced-commands-only</em>).</p>
<pre>user@server:~&gt; ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/user/.ssh/id_rsa.
Your public key has been saved in /home/user/.ssh/id_rsa.pub.
The key fingerprint is:
f2:13:a7:23:75:da:4e:35:a7:32:61:af:43:e1:a0:53 user@server</pre>
<p>Copy what is contained in <code>id_rsa.pub</code>.  Now, on the Drobo we have to create the authoized_keys file to enable us to login with the key we just created.  The required directory and files do not exist by default so, from the Drobo:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #c20cb9; font-weight: bold;">mkdir</span> ~<span style="color: #000000; font-weight: bold;">/</span>.ssh
<span style="color: #c20cb9; font-weight: bold;">vi</span> ~<span style="color: #000000; font-weight: bold;">/</span>.ssh<span style="color: #000000; font-weight: bold;">/</span>authorized_keys</pre></div></div>

<p>Now copy the contents of id_rsa.pub into the authorized_keys file.  You should now be able to SSH from the server to the Drobo without needing to enter a password.</p>
<p>Now that our apps are setup on the Drobo and communications will be seamless, we&#8217;re going to create two scripts, one to rotate and manage the backups on the Drobo and one to do the actual rsync-ing.</p>
<p>The first script will reside on the Drobo in the root of the backup folder.  In our example:</p>
<pre>/mnt/DroboFS/Shares/backup</pre>
<p>I&#8217;ll call it rotate_backups.sh and it&#8217;ll look like the following.  Edit as necessary for your own purposes, and please note, the Drobo doesn&#8217;t have the bash shell, so we&#8217;re using good old #!/bin/sh</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">#!/bin/sh</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># How many backups would you like to keep, each time you run</span>
<span style="color: #666666; font-style: italic;"># the backup script, a new one will be created, so if you want:</span>
<span style="color: #666666; font-style: italic;"># Daily for a week, script goes cron daily and enter 7.</span>
<span style="color: #666666; font-style: italic;"># Hourly for 3 days, script goes cron hourly and enter 72 (24 hours x 3 days)</span>
<span style="color: #007800;">NUMOFBACKUPS</span>=<span style="color: #000000;">7</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># Where are we backing up to?</span>
<span style="color: #007800;">BACKUPLOC</span>=<span style="color: #000000; font-weight: bold;">/</span>mnt<span style="color: #000000; font-weight: bold;">/</span>DroboFS<span style="color: #000000; font-weight: bold;">/</span>Shares<span style="color: #000000; font-weight: bold;">/</span>backup
&nbsp;
<span style="color: #666666; font-style: italic;"># Delete the oldest backup</span>
<span style="color: #007800;">NUMOFBACKUPS</span>=<span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">expr</span> <span style="color: #007800;">$NUMOFBACKUPS</span> - <span style="color: #000000;">1</span><span style="color: #000000; font-weight: bold;">`</span>
<span style="color: #000000; font-weight: bold;">if</span> <span style="color: #7a0874; font-weight: bold;">&#91;</span> <span style="color: #660033;">-d</span> <span style="color: #007800;">$BACKUPLOC</span><span style="color: #000000; font-weight: bold;">/</span>backup.<span style="color: #007800;">$NUMOFBACKUPS</span> <span style="color: #7a0874; font-weight: bold;">&#93;</span>; <span style="color: #000000; font-weight: bold;">then</span>
        <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">&quot;delete backup.<span style="color: #007800;">$NUMOFBACKUPS</span>&quot;</span>
        <span style="color: #c20cb9; font-weight: bold;">rm</span> <span style="color: #660033;">-Rf</span> <span style="color: #007800;">$BACKUPLOC</span><span style="color: #000000; font-weight: bold;">/</span>backup.<span style="color: #007800;">$NUMOFBACKUPS</span>
<span style="color: #000000; font-weight: bold;">fi</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># Move each snapshot</span>
<span style="color: #000000; font-weight: bold;">while</span> <span style="color: #7a0874; font-weight: bold;">&#91;</span> <span style="color: #007800;">$NUMOFBACKUPS</span> <span style="color: #660033;">-gt</span> <span style="color: #000000;">0</span> <span style="color: #7a0874; font-weight: bold;">&#93;</span>
<span style="color: #000000; font-weight: bold;">do</span>
        <span style="color: #007800;">NUMOFBACKUPS</span>=<span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">expr</span> <span style="color: #007800;">$NUMOFBACKUPS</span> - <span style="color: #000000;">1</span><span style="color: #000000; font-weight: bold;">`</span>
        <span style="color: #000000; font-weight: bold;">if</span> <span style="color: #7a0874; font-weight: bold;">&#91;</span> <span style="color: #660033;">-d</span> <span style="color: #007800;">$BACKUPLOC</span><span style="color: #000000; font-weight: bold;">/</span>backup.<span style="color: #007800;">$NUMOFBACKUPS</span> <span style="color: #7a0874; font-weight: bold;">&#93;</span> ; <span style="color: #000000; font-weight: bold;">then</span>
                <span style="color: #007800;">NEW</span>=<span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">expr</span> <span style="color: #007800;">$NUMOFBACKUPS</span> + <span style="color: #000000;">1</span><span style="color: #000000; font-weight: bold;">`</span>
                <span style="color: #c20cb9; font-weight: bold;">mv</span> <span style="color: #007800;">$BACKUPLOC</span><span style="color: #000000; font-weight: bold;">/</span>backup.<span style="color: #007800;">$NUMOFBACKUPS</span> <span style="color: #007800;">$BACKUPLOC</span><span style="color: #000000; font-weight: bold;">/</span>backup.<span style="color: #007800;">$NEW</span>
                <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">&quot;Move backup.<span style="color: #007800;">$NUMOFBACKUPS</span> to backup.<span style="color: #007800;">$NEW</span>&quot;</span>
        <span style="color: #000000; font-weight: bold;">fi</span>
<span style="color: #000000; font-weight: bold;">done</span></pre></div></div>

<p>This script will delete your oldest backup and move the others down the line&#8230; so your latest backup will always be backup.0, 1 run old is backup.1, 2 runs old is backup.2, etc.</p>
<p>Now that our backups are managed properly we will create the actual rsync script.  This will be run on the computer you want to back up from, and I suggest you add it to the crontab so it runs as often as you want it to.  Edit BDIR, BSERVER, REMOVEDIR, and EXCLUDES as necessary.</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">#!/bin/sh</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># Directory to backup.</span>
<span style="color: #007800;">BDIR</span>=<span style="color: #000000; font-weight: bold;">/</span>data_to_backup
&nbsp;
<span style="color: #666666; font-style: italic;"># Remote server (should match the passwordless SSH credentials</span>
<span style="color: #666666; font-style: italic;"># we setup earlier).</span>
<span style="color: #007800;">BSERVER</span>=root<span style="color: #000000; font-weight: bold;">@</span>drobo
&nbsp;
<span style="color: #666666; font-style: italic;"># Full path of the directory on the Drobo to backup to.</span>
<span style="color: #007800;">REMOTEDIR</span>=<span style="color: #000000; font-weight: bold;">/</span>mnt<span style="color: #000000; font-weight: bold;">/</span>DroboFS<span style="color: #000000; font-weight: bold;">/</span>Shares<span style="color: #000000; font-weight: bold;">/</span>backup
&nbsp;
<span style="color: #666666; font-style: italic;"># A list of files or directories to exclude.</span>
<span style="color: #007800;">EXCLUDES</span>=<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>brite<span style="color: #000000; font-weight: bold;">/</span>backup<span style="color: #000000; font-weight: bold;">/</span>excludes.txt
&nbsp;
<span style="color: #666666; font-style: italic;"># Activate our rotate_backups script on the drobo.</span>
<span style="color: #c20cb9; font-weight: bold;">ssh</span> <span style="color: #007800;">$BSERVER</span> <span style="color: #007800;">$REMOTEDIR</span><span style="color: #000000; font-weight: bold;">/</span>rotate_backups.sh
&nbsp;
<span style="color: #007800;">OPTS</span>=<span style="color: #ff0000;">&quot;--force --ignore-errors --delete-excluded --exclude-from=<span style="color: #007800;">$EXCLUDES</span> --delete -av --rsync-path=/mnt/DroboFS/Shares/DroboApps/rsync/rsync&quot;</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># Do the rsync.</span>
rsync <span style="color: #007800;">$OPTS</span> <span style="color: #660033;">-e</span> <span style="color: #ff0000;">'ssh -p 22'</span> <span style="color: #660033;">--link-dest</span>=..<span style="color: #000000; font-weight: bold;">/</span>backup.1 <span style="color: #007800;">$BDIR</span> <span style="color: #007800;">$BSERVER</span>:<span style="color: #007800;">$REMOTEDIR</span><span style="color: #000000; font-weight: bold;">/</span>backup.0<span style="color: #000000; font-weight: bold;">/</span></pre></div></div>

<p>Add that script to your hourly/daily/whatever you want crontab and you should be good.  The first time you run it you&#8217;ll be transferring the entire backup directory so it might take a long time and you&#8217;ll have an error message about <code>--link-dest backup.1</code> not existing, but you can ignore that.  Subsequent backups should run without a hitch.</p>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/KqYpcd77Aqo" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/drobo-incremental-rsync-backups/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/drobo-incremental-rsync-backups/</feedburner:origLink></item>
		<item>
		<title>Are you an Interface Designer &amp; Graphic Artist?</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/6R2JH_jj1lY/</link>
		<comments>http://www.bryanrite.com/are-you-an-interface-designer-graphic-artist/#comments</comments>
		<pubDate>Fri, 25 Mar 2011 00:36:08 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[News]]></category>
		<category><![CDATA[job]]></category>

		<guid isPermaLink="false">http://www.bryanrite.com/?p=789</guid>
		<description><![CDATA[We&#8217;re looking for a great interface and graphic artist interested in working with our collective and getting in on some really exciting opportunities and projects. Is This a Job? No, ...]]></description>
			<content:encoded><![CDATA[<p>We&#8217;re looking for a <em>great</em> interface and graphic artist interested in working with our collective and getting in on some really exciting opportunities and projects.</p>
<h3>Is This a Job?</h3>
<p>No, not really.  What we&#8217;re looking for is a person interested in working on projects with us, getting in at the ground floor and collaborating on ideas brought to the table by our collective and external clients.</p>
<p>A lot of us have day jobs, these are projects and tasks we work on in our spare time.  They are passion projects for some, and great money-making ideas for others.  All projects are run on an opt-in basis, meaning you only work on what you want to be a part of.</p>
<p><span id="more-789"></span></p>
<h3>How Does this Work?</h3>
<p>You would complement our team of developers and marketers by designing the graphics and interfaces used on websites, mobile applications, etc, in a collaborative environment.</p>
<p>We have a very informal atmosphere because the people working with you are extremely passionate about what they do.  We chat over beers at the pub, sit down at a whiteboard on the weekend, or skype together.</p>
<p>As part of a self managing collective, you would be expected to live up to your word; we ask for nothing else.</p>
<h3>How Much Does it Pay?</h3>
<p>A lot of the ideas brought forth by our collective are bootstrapped, meaning the people involved aren&#8217;t paid upfront but the revenue of our hard work is typically shared equally between those involved.</p>
<p>Some projects are brought to us by clients, and revenue sharing is openly discussed before projects are taken on.</p>
<h3>What Do I Need To Have?</h3>
<p>The ability to create beautiful logos, graphics, and interfaces any way you deem necessary.  Design the look and feel of projects through collectively contributed mood boards and ideas.</p>
<p>Design with interactivity, simplicity, beauty, and passion!</p>
<h3>Conclusion</h3>
<p>It&#8217;s an opportunity to work on projects and bring all our diverse skills together and create world-class products and projects.  If you&#8217;re interested, contact me at <a href="mailto:bryan@bryanrite.com">bryan@bryanrite.com</a></p>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/6R2JH_jj1lY" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/are-you-an-interface-designer-graphic-artist/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/are-you-an-interface-designer-graphic-artist/</feedburner:origLink></item>
		<item>
		<title>Creating friendly UTF-8 Slug URLS</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/kAE6uijDGdQ/</link>
		<comments>http://www.bryanrite.com/creating-friendly-utf-8-slug-urls/#comments</comments>
		<pubDate>Mon, 30 Mar 2009 16:56:08 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[seo]]></category>
		<category><![CDATA[utf-8]]></category>

		<guid isPermaLink="false">http://www.brybot.ca/archives/130-creating-friendly-utf-8-slug-urls/</guid>
		<description><![CDATA[A slug is a SEO-friendly, human-readable version of a URL. Generally used on most blog software for permalinks via a blog&#8217;s title (exactly like my blog here), or basically any ...]]></description>
			<content:encoded><![CDATA[<p>A slug is a SEO-friendly, human-readable version of a URL.  Generally used on most blog software for permalinks via a blog&#8217;s title (exactly like my blog here), or basically any string you want to turn into a friendly URL.  Sure you could just use PHP&#8217;s <code>urlencode</code> (or other language equivalent) but then you&#8217;re stuck with unfriendly characters translated into hex codes: <code>%2F%20</code></p>
<p>The problem is greater when the content you want to Slug is UTF-8 encoded and contains non-ASCII characters.  How do you slug a word like: <code>Iñtërnâtiônàlizætiøn</code>?</p>
<p><span id="more-130"></span></p>
<p>My ongoing redo of <a href="http://www.footstops.com">Footstops</a>, which now creates slug&#8217;d URLs from UTF-8 user generated content, has ventured me into such territory and I&#8217;ll share my slug method with you.  The one caveat is that its power relies on the awesome <code>iconv</code> library, which has come enabled by default since PHP 5.0.0, and easily installable in PHP 4.2+, so make sure you have that, if not, remove the line &#8211; it still works, just not nearly as well.  I also make the assumption that your data is encoded in UTF-8, which is fairly safe because it is pretty backward compatible, but if you are working in a different charset, please adjust as necessary.</p>
<p>The method is short and sweet.</p>
<p>1. First we use <code>iconv</code> to <em>translit</em> the UTF-8 string into ASCII.  This converts the UTF-8 string into an ASCII equivalent, but also translate non-ASCII characters into their ASCII appearing equivalents: ë becomes e.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #990000;">iconv</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'UTF-8'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'ASCII//TRANSLIT//IGNORE'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$string</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>2. We then remove all the unwanted characters from the URL.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #990000;">preg_replace</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/[^a-zA-Z0-9 -]/'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">''</span><span style="color: #339933;">,</span> <span style="color: #000088;">$url</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>3. We convert it to lowercase (which is just a preference for consistency), make sure its between our max string length (we don&#8217;t want a 64 character slug, 40-50 characters is probably lots), and remove any surrounding whitespace.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #990000;">trim</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">substr</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">strtolower</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$url</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">,</span> <span style="color: #000088;">$maxLength</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>4. Finally we replace any whitespace or our separator character with a single instance of the separator character, to remove multiples.  I prefer an <em>underscore</em> as a word separator rather than a dash (traditional slug separator) as it may conflict with an actual hyphen in the string but in the final version you&#8217;ll see it&#8217;s easy to default to your own preference.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #990000;">preg_replace</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/[s'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$separator</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">']+/'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$separator</span><span style="color: #339933;">,</span> <span style="color: #000088;">$url</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>So we put it all together with a couple of options:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">public</span> static <span style="color: #000000; font-weight: bold;">function</span> ToSlug<span style="color: #009900;">&#40;</span><span style="color: #000088;">$string</span><span style="color: #339933;">,</span> <span style="color: #000088;">$maxLength</span><span style="color: #339933;">=</span><span style="color: #cc66cc;">40</span><span style="color: #339933;">,</span> <span style="color: #000088;">$separator</span><span style="color: #339933;">=</span><span style="color: #0000ff;">'_'</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
	<span style="color: #000088;">$url</span> <span style="color: #339933;">=</span> <span style="color: #990000;">iconv</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'UTF-8'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'ASCII//TRANSLIT//IGNORE'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$string</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #000088;">$url</span> <span style="color: #339933;">=</span> <span style="color: #990000;">preg_replace</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/[^a-zA-Z0-9 -]/'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">''</span><span style="color: #339933;">,</span> <span style="color: #000088;">$url</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #000088;">$url</span> <span style="color: #339933;">=</span> <span style="color: #990000;">trim</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">substr</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">strtolower</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$url</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">,</span> <span style="color: #000088;">$maxLength</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #000088;">$url</span> <span style="color: #339933;">=</span> <span style="color: #990000;">preg_replace</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/[s'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$separator</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">']+/'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$separator</span><span style="color: #339933;">,</span> <span style="color: #000088;">$url</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #b1b100;">return</span> <span style="color: #000088;">$url</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>Calling <code>ToSlug('Iñtërnâtiônàlizætiøn    is the greatest! ')</code> produces the slug:</p>
<p><code>internationalizaetion_is_the_greatest</code></p>
<p>perfect for a friendly URL!</p>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/kAE6uijDGdQ" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/creating-friendly-utf-8-slug-urls/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/creating-friendly-utf-8-slug-urls/</feedburner:origLink></item>
		<item>
		<title>Getting Imagemagick (and more) to work with MAMP on OS X</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/BNP82zvTREQ/</link>
		<comments>http://www.bryanrite.com/getting-imagemagick-and-more-to-work-with-mamp-on-os-x/#comments</comments>
		<pubDate>Mon, 16 Feb 2009 20:52:03 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[imagemagick]]></category>
		<category><![CDATA[mamp]]></category>
		<category><![CDATA[os x]]></category>

		<guid isPermaLink="false">http://www.brybot.ca/archives/122-getting-imagemagick-and-more-to-work-with-mamp-on-os-x/</guid>
		<description><![CDATA[I&#8217;ve been using MAMP on my macbook for doing my web development at home and can&#8217;t say enough good things about it. Unfortunately I had to start using some binaries ...]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been using MAMP on my macbook for doing my web development at home and can&#8217;t say enough good things about it.  Unfortunately I had to start using some binaries not included with MAMP, ImageMagick for example.</p>
<p><em>Update: Commentors have been reporting this fix is not working on Snow Leopard or MAMP 1.8.4</em></p>
<p>Installing ImageMagick via MacPorts is simple enough:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #c20cb9; font-weight: bold;">sudo</span> port <span style="color: #c20cb9; font-weight: bold;">install</span> ImageMagick</pre></div></div>

<p>but trying to run it via the MAMP stack gives you the apache error:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;">dyld: Symbol not found: __cg_jpeg_resync_to_restart
  Referenced from: <span style="color: #000000; font-weight: bold;">/</span>System<span style="color: #000000; font-weight: bold;">/</span>Library<span style="color: #000000; font-weight: bold;">/</span>Frameworks<span style="color: #000000; font-weight: bold;">/</span>ApplicationServices.framework<span style="color: #000000; font-weight: bold;">/</span>Versions<span style="color: #000000; font-weight: bold;">/</span>A<span style="color: #000000; font-weight: bold;">/</span>Frameworks<span style="color: #000000; font-weight: bold;">/</span>ImageIO.framework<span style="color: #000000; font-weight: bold;">/</span>Versions<span style="color: #000000; font-weight: bold;">/</span>A<span style="color: #000000; font-weight: bold;">/</span>ImageIO
  Expected <span style="color: #000000; font-weight: bold;">in</span>: <span style="color: #000000; font-weight: bold;">/</span>Applications<span style="color: #000000; font-weight: bold;">/</span>MAMP<span style="color: #000000; font-weight: bold;">/</span>Library<span style="color: #000000; font-weight: bold;">/</span>lib<span style="color: #000000; font-weight: bold;">/</span>libjpeg.62.dylib</pre></div></div>

<p>or something similar. <span id="more-122"></span> I was able to get it to go, but you need to break the sandbox, so it may not be a viable solution for everyone.</p>
<p>Once ImageMagick is installed, head to:</p>
<p><code>/Applications/MAMP/Library/bin/envvars</code></p>
<p>Here we want to comment out the two lines:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">#DYLD_LIBRARY_PATH=&quot;/Applications/MAMP/Library/lib:$DYLD_LIBRARY_PATH&quot;</span>
<span style="color: #666666; font-style: italic;">#export DYLD_LIBRARY_PATH</span></pre></div></div>

<p>and add the line:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #7a0874; font-weight: bold;">export</span> <span style="color: #007800;">PATH</span>=<span style="color: #ff0000;">&quot;<span style="color: #007800;">$PATH</span>:/opt/local/bin&quot;</span></pre></div></div>

<p>What we&#8217;re doing is using our systems default DYLD Library path, not the sandboxed one, and exporting our MacPorts bin directory into the sandbox PATH, so it can execute your ImageMagick bins like convert without specifying the full path to it.</p>
<p>Presto!  I&#8217;m not sure of the full consequences of doing this but I haven&#8217;t run into any problems yet and nothing seems to break as a result.  Always backup the original files tho!</p>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/BNP82zvTREQ" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/getting-imagemagick-and-more-to-work-with-mamp-on-os-x/feed/</wfw:commentRss>
		<slash:comments>32</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/getting-imagemagick-and-more-to-work-with-mamp-on-os-x/</feedburner:origLink></item>
		<item>
		<title>Updating Subversion to 1.5 on Ubuntu Hardy Heron</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/S2DT-FZfN7U/</link>
		<comments>http://www.bryanrite.com/updating-subversion-to-15-on-ubuntu-hardy-heron/#comments</comments>
		<pubDate>Fri, 09 Jan 2009 14:15:00 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[apt]]></category>
		<category><![CDATA[pinning]]></category>
		<category><![CDATA[subversion]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://www.brybot.ca/archives/129-updating-subversion-to-15-on-ubuntu-hardy-heron/</guid>
		<description><![CDATA[I was perusing the Ubuntu Hardy Heron backports repository and noticed that subversion had been added to the backports. If you&#8217;re reading this, you probably already know that you&#8217;re stuck ...]]></description>
			<content:encoded><![CDATA[<p>I was perusing the Ubuntu Hardy Heron <a href="http://packages.ubuntu.com/hardy-backports/">backports repository</a> and noticed that subversion had been added to the backports.  If you&#8217;re reading this, you probably already know that you&#8217;re stuck with subversion 1.4 officially and unless you wanted to compile from source, or install a 3rd party <code>.deb</code>, you were pretty much out of luck.</p>
<p>Now that its been added to the backports repository, its a very simple to upgrade.</p>
<p><span id="more-129"></span></p>
<p>If you&#8217;re never added anything from backports before, listen up.  Backports are all the libs that have feature upgrades (not security related) that won&#8217;t show up in normal <code>apt-get upgrade</code> repositories.  You could simply add the following line to your <code>sources.list</code>:</p>
<pre>deb http://archive.ubuntu.com/ubuntu hardy-backports main universe multiverse restricted</pre>
<p>On a default Hardy install it should already be in there, just commented out.  So you can uncomment the two backports lines.</p>
<p>This will make <em>all</em> the libs on your machine upgrade to the newest&#8230; probably not what you want as you won&#8217;t get security updates for the backported versions.  Instead we can use something called <strong>Pinning</strong>, which is available on most <code>apt</code> based operating system.</p>
<p>What we do is add the repository to our <code>sources.list</code> as above but put its priority lower then default, so that the normal repositories take precedent.  Then we can on, a per lib basis, upgrade only the libraries we expressly want upgraded from backports.</p>
<p>In the file <code>/etc/apt/preferences</code> (you may have to create it), add the following lines:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;">Package: <span style="color: #000000; font-weight: bold;">*</span>
Pin: release <span style="color: #007800;">a</span>=hardy-backports
Pin-Priority: <span style="color: #000000;">400</span></pre></div></div>

<p>We&#8217;re setting the hardy-backports repository to have a lower then default priority, so it won&#8217;t override the official repositories.</p>
<p>Update your apt cache: <code>sudo apt-get update</code></p>
<p>Now if you do an <code>apt-get upgrade subversion</code> you shouldn&#8217;t see any changes&#8230; so how do we install from backports?</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #c20cb9; font-weight: bold;">sudo</span> <span style="color: #c20cb9; font-weight: bold;">apt-get</span> upgrade <span style="color: #660033;">-t</span> hardy-backports subversion</pre></div></div>

<p>You can see, its the same line, except we set the <em>target</em> of the upgrade to come from the backports repository.</p>
<p>Now you can keep your servers on the main repositories but still update a library here and there if you really desire new functionality.</p>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/S2DT-FZfN7U" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/updating-subversion-to-15-on-ubuntu-hardy-heron/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/updating-subversion-to-15-on-ubuntu-hardy-heron/</feedburner:origLink></item>
		<item>
		<title>Repairing a Faulty Disk in a Software RAID Array</title>
		<link>http://feedproxy.google.com/~r/BryanRite/~3/kUag8GtVqPk/</link>
		<comments>http://www.bryanrite.com/repairing-a-faulty-disk-in-a-software-raid-array/#comments</comments>
		<pubDate>Wed, 31 Dec 2008 10:40:22 +0000</pubDate>
		<dc:creator>Bryan Rite</dc:creator>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[RAID]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://www.brybot.ca/archives/128-repairing-a-faulty-disk-in-a-software-raid-array/</guid>
		<description><![CDATA[I was doing some system maintenance today and came across the following horrific screen: /dev/md0: Version : 00.90.03 Creation Time : Sun Nov 16 14:13:20 2007 Raid Level : raid5 ...]]></description>
			<content:encoded><![CDATA[<p>I was doing some system maintenance today and came across the following horrific screen:</p>
<pre>/dev/md0:
        Version : 00.90.03
  Creation Time : Sun Nov 16 14:13:20 2007
     Raid Level : raid5
     Array Size : 732587712 (698.65 GiB 750.17 GB)
  Used Dev Size : 244195904 (232.88 GiB 250.06 GB)
   Raid Devices : 4
  Total Devices : 4
Preferred Minor : 0
    Persistence : Superblock is persistent

    Update Time : Wed Dec 31 10:41:15 2008
          State : clean, degraded
 Active Devices : 3
Working Devices : 3
 Failed Devices : 1
  Spare Devices : 0

...

    Number   Major   Minor   RaidDevice State
       0       0        0        0      removed
       1       8       17        1      active sync   /dev/sdb1
       2       8       33        2      active sync   /dev/sdc1
       3       8       49        3      active sync   /dev/sdd1
       4       8        1        -      faulty spare</pre>
<p>One of the drives in my fileserver had died!  Time to back up and get that sucker running again.</p>
<p><strong><em>Please note:</em></strong> The following is only a guide to help you replace a failed disc.  I cannot guarantee this will work for you, but it is what I do and has worked every time without any data loss.</p>
<p>As you can see, it is a 4 disc software RAID 5 array with no hotswap spares.  The following should work for most single disc failure situations in RAID 1, 5, or 6.</p>
<p>It appears that <code>sda</code> has bailed on me.  First things first, <strong>backup the machine</strong>.  If anything happens, you can rebuild from scratch.</p>
<p>You can see the faulty disc has already been removed from the array, but if yours hasn&#8217;t been removed yet, the commands:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;">mdadm <span style="color: #660033;">--manage</span> <span style="color: #000000; font-weight: bold;">/</span>dev<span style="color: #000000; font-weight: bold;">/</span>md0 <span style="color: #660033;">-f</span> <span style="color: #000000; font-weight: bold;">/</span>dev<span style="color: #000000; font-weight: bold;">/</span>sda1
mdadm <span style="color: #660033;">--manage</span> <span style="color: #000000; font-weight: bold;">/</span>dev<span style="color: #000000; font-weight: bold;">/</span>md0 <span style="color: #660033;">-r</span> <span style="color: #000000; font-weight: bold;">/</span>dev<span style="color: #000000; font-weight: bold;">/</span>sda1</pre></div></div>

<p>will mark it as failed (so it can be removed) and remove the <code>sda1</code> partition.</p>
<p>Shutdown the machine and switch out the harddrives, make sure you only replace the faulty drive, don&#8217;t mess up the order of the drives cause it&#8217;ll be a pain to get it back in.</p>
<p>Boot up the machine.  Your RAID array will be in the same degraded state.  We need to partition the new drive exactly the same way we partitioned the drives in the existing array.  Luckily this is a one-liner with <code>sfdisk</code>:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;">sfdisk <span style="color: #660033;">-d</span> <span style="color: #000000; font-weight: bold;">/</span>dev<span style="color: #000000; font-weight: bold;">/</span>sdb <span style="color: #000000; font-weight: bold;">|</span> sfdisk <span style="color: #000000; font-weight: bold;">/</span>dev<span style="color: #000000; font-weight: bold;">/</span>sda</pre></div></div>

<p>The above code will dump the partition table of <code>sdb</code> (or use any of the functioning drives) and pipe it to <code>sfdisk</code> to partition <code>sda</code> the same way.  It should only take a second.</p>
<p>Then we can simply add the new drive to the array:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;">mdadm <span style="color: #660033;">--manage</span> <span style="color: #000000; font-weight: bold;">/</span>dev<span style="color: #000000; font-weight: bold;">/</span>md0 <span style="color: #660033;">-a</span> <span style="color: #000000; font-weight: bold;">/</span>dev<span style="color: #000000; font-weight: bold;">/</span>sda1</pre></div></div>

<p>Bam!  If you take a look at <code>cat /proc/mdstat</code> or <code>mdadm --detail /dev/md0</code> you should see that the array is recovering (with a percentage done).</p>
<p>After the recovery is done, you&#8217;ll be back to new and clean!</p>
<p>Good luck!</p>
<img src="http://feeds.feedburner.com/~r/BryanRite/~4/kUag8GtVqPk" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://www.bryanrite.com/repairing-a-faulty-disk-in-a-software-raid-array/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		<feedburner:origLink>http://www.bryanrite.com/repairing-a-faulty-disk-in-a-software-raid-array/</feedburner:origLink></item>
	</channel>
</rss>

