Half-Elf on Tech

Thoughts From a Professional Lesbian

Author: Ipstenu (Mika Epstein)

  • My Question is “You Suck”

    My Question is “You Suck”

    If you’ve ever watched Survivor, they have a Tribal Council at the end of each episode, where they discuss things and decide who to vote off the island. In the grand finale, however, they instead discuss whom to give the million dollars. Every previously voted off tribe member gets a chance to ask the two (or three) finalists a question. Miss Alli, from the classic days of Television Without Pity, used to love/hate when someone would step up and use their time to shout, insult, or otherwise berate the finalists. She called that “My Question is you suck.”

    Whenever you do support, you will invariably run into people who act exactly like that. If I had a dollar for every time someone left a support ticket with “This sucks!” I wouldn’t need to work anymore. And those people are really annoying, because you want to reply “Well thanks, and over here in the constructive world…” but you absolutely cannot engage them. As the Survivor yahoos quickly learned, feeding the fuel for someone who’s ranting is about as useful as keeping the rain off with those little paper umbrellas you get with fruity drinks.

    My question is … this code is crap

    Something to keep in mind, Tech Support is not “Customer Service” per se. When someone needs tech support, while they totally need someone to be ‘nice’ to them, they really need someone to fix their problem. Customer Service is all about building a relationship with the customer, figuring out their needs and wants, and basically selling them something. On the other hand, tech support is being told “This is my problem, fix it.” While that certainly happens to people who work at hotel desks (“There’s no hot water…”) it’s the meat and milk of life for anyone who writes and/or maintains software.

    To this end, you’re really not ‘servicing’ the customer, nor are you taking time to build a great rapport with them, you’re trying to fix what’s wrong. Certainly doing this provides a service to the customer, but making sure the person comes back (or stays) is often secondary. After all, if we can fix the problem, you’ll come back, right?

    When someone just says ‘You suck’ or ‘Your code sucks’ there’s very little you can do about it, if they’re not willing to give you a concrete example of these things. I have, on occasion, replied “Patches welcome! I’m always happy to improve my work.” That’s pretty much the best I can do. When the reply is that I, personally, suck, then I hand it over to anyone else. There’s nothing I can do here, so it’s time to ask someone else to help me out.(If you’re the solo dev, it’s time to cut your losses, say that you’re sorry you can’t help them, and walk away.)

    My question is …. this plugin gets one star because it doesn’t work

    How Jaquith Reviews Code
    Mark Jaquith Reviews Code. Credit Mark Jaquith

    Everyone hates the ‘review that should have been a support question.’ Invariably we’ll get it. The plugin doesn’t work, screw you. And when we go back to look at the person’s post history, we see never once did they, in a place you can find, ask you ‘How do I make this work?’ It’s frustrating. Now you should keep in mind, on the WordPress.org forums, someone can change their star rating, so the best thing to do here is try and win them back. Kill ’em with a little kindness, point out ‘I would have seen this faster had you…’

    But in general, this one is not to terrible to win back. Much of the time, someone who is this lost that they can only find the review location and not the right support places is someone who has a pretty easy to fix issue. Solve it and you’ve got a returning user. A smaller percentage of the time, alas, the problem is someone who really, truly, didn’t read what the plugin does. “This plugin for vegan resources sucks because there’s no bacon!” Not much you can do there except point out “This is by design.”

    My question is …. you don’t reply fast enough for me

    Today we expect, and often get, instant feedback. We have livechats, we have Twitter and Facebook. We reach out to the people who represent a company (or a TV Show), and we assume there will be some prompt reaction. This is no longer customer ‘service’ but ‘experience.’ The customer’s personal experience will color their feelings about both the product and the people behind it. It’s a large part of why I don’t think pure customer service exists any more, if it ever did, in software.

    The problem comes in when you take a weekend off, or an afternoon, because you want to actually have, you know, some time with your family, or go to a movie. Maybe you were just asleep for eight consecutive hours. Either way, it was the worst time in the world for someone else, and they’ve left multiple requests for help. If you’ve ever worked in a ‘traditional’ office, these people are like the guy who emails you a long question, and then calls you and swings by your desk, after IMing you, to make sure you got the email.

    In short, they hit every single ‘annoyance’ nerve in a person’s body, all at once, and they do it over and over and over. I tend to want to reach through the monitor and take away their caffeine for a couple days. But until someone invents that for me, I have a couple tactics.

    For anything ‘free’ (like WordPress plugins) I tell them that this is a free product I write in my free time, and that means waiting a reasonable time for a reply means waiting 3-5 days, not 3-5 seconds. And then I answer their question as best I can. For the paid stuff, I point out that I missed their email because I had gone home for the day (or ‘had the weekend off’). Usually just that gentle reminder of “some people do work ‘normal’ hours still” gets them off their horse enough to work with. On the rare occasion it doesn’t(I’ve had people tell me that I’m too important to not be available 24/7, which is sweet, but no.) I’ve just ignored their unreasonable demands and concentrated on fixing the problem at hand.

    My question is … you don’t know what you’re talking about

    You SuckFinally there’s the you suck hidden in a peculiar phrasing of basically “you aren’t good enough.” This is not the same as the outright “You suck.” because it’s actually a value judgement. It’s not a dismissive “You’re a meanie poopy head!” sort of claim, it’s a “Your code sucks!” And this sort of comment hurts a lot. People calling you names rarely have any basis to do so, and they’re rarely right. People calling into question your work, however, that cuts to the bone and tends to make us over react.

    In defense of people who submit bug reports, much of the time this is not what they mean! Sometimes they say “I think you’re wrong because of XYZ” and it comes across as “You idiot, how could you not possibly know this!” I say this a lot, but text is a really lousy medium for communication of intent. Now. One thing the people who report the bugs really need to remember to do is say “Thank you” when the bug is fixed, or even “I appreciate you taking the time to explain to me why you won’t do XYZ.” Instead, most of what we get is someone saying “I told you! I was right and you were wrong!” I gotta tell you, that never really makes me want to work with you again.

    But the real reason this one galls me is that it’s generally said to me after someone has specifically asked me for help on something of I’m somewhat of an expert (or talented tweaker). I’ve had people tell me I don’t know jack about WordPress (pretty sure that’s wrong), or pretty much anything else under the sun. That frustrates me, since you came over here asking me, admitedly a total stranger, for help, and when I gave it you snipped that I’m ignorant.

    To these, I actually do point out “You know, you asked me for my help/opinion and I gave it. We can agree to disagree, but you don’t have to be mean about it, since that’s not a good way to get help from a free, volunteer, community.”

    Unless of course you’ve hired me, at which point I refund most of your money and cancel the contract.

  • When the lights go out on the Matt Report!

    When the lights go out on the Matt Report!

    Matt Mederios interviewed me ages ago. And this post I thought I’d pressed publish on and totally didn’t… Uh. Okay, sorry, Matt, consider this a late traffic bump for you.

    When the lights go out with Mika Epstein back in March literally had a power issue (the office I borrowed has a faulty sensor and timer apparently, I had no idea!) so I was in the dark for a while. Of course. But I was happy to talk about WordPress support and doing our best by you!

    I had a great time talking to Matt. I’m always nervous being interviewed, and he made it painless.

  • Review: DesktopServer

    Review: DesktopServer

    I’ve been a MAMP user for years, but at WordCamp Chicago, Suzette Franck handed me a thumbdrive with free version of DesktopServer on it. Interested (and not just because a thumbdrive with an app on it is the way I expect to be handed software today), I installed it once I got back to my office.

    Installation and Setup

    You can’t run the zip from the the thumbdrive. It’s a 250meg drive with only 50 megs to spare, so because a Mac is silly, I had to copy the files locally and then unzip. It would be ‘better’ (and this is subjective) if the drive had the installer apps instead of the zips. That said, I know they were copying the zips to the thumbdrives manually so I totally give them a pass.

    Once I did unzip it, it said it would have to restart. So I shut down everything, installed, and … no restart. I think it was a poor choice of words:

    ds-restart

    Right, clearly I wasn’t paying attention. Still, it’s a strange thing to warn when it didn’t actually happen! Now, I know what they meant is this:

    ds-start-admin

    I hate that MAMP does this too, and have long used the MAMP No Password app to work around this. I’d love to see that added to Desktop Server.

    There were a lot of click-throughs, but that it let me add and create a dev website, complete with the hosts file, was nice. I liked that I was able to easily tell it to install in Sites (where I like my sites!), but I didn’t like that it’s bundled with WordPress 3.5.1:

    installnewsite

    Actually I don’t like that it’s bundled with WordPress. Blasphemy! But really, it would be killer if it could just have a dropdown of options: WordPress, Drupal, etc. Then when it runs, it grabs from the latest build. WordPress does this with latest.zip. Drupal doesn’t. Didn’t know that. You should, Drupal! For now, people have come up with an interesting wget based solution, so that would be an awesome thing to add. But I clicked WP (vs nothing), which is the default. I was directed to my site, where all I had to fill in was username and such. All the DB work was done!

    Using DesktopServer

    At this point, I was done so I closed DesktopServer.

    done

    My site, elftest.dev, was up and running and it was easy to use. Perfect! Seriously, at this point it just worked. If I wanted to spin up another site, that was easy too.

    Sadly, it’s not easy to make a subdomain site ala Multisite. I wanted to add ‘foo.elftest.dev’ and I tried to select this be installed in elftest.dev, but got this instead:

    subdomain

    That’s okay for me, I know how to use my hosts file, but it would be nice to have this more accessible. After all, the draw of DesktopServer is that it’s easy right?

    Shutting down DesktopServer

    Here it’s weird. I had shut down the app, because it was done. But doing this only closes the app, not the Apache instance, so I had to reopen (and ‘reboot’ it with Admin rights again) to be presented with this:

    shutdown

    Now I could shut down.

    Conclusions

    It’s a split decision. When I’m using the “MAMP no password” app, it’s certainly easier to activate and deactivate MAMP. However, DesktopServer wins with ease to spin up new sites. For someone like me, who needs to quickly test sites for customers and likes to use foo.dev or foo.loc to test, this is great. I don’t need to hassle with anything, just turn it on, add a site, done. DesktopServer is a little in the middle between easy for newbies and awesome for devs. It’s got a lot of room where I could see it growing, and that’s enough to keep me using it

    The downsides are:

    • Have to ‘restart’ the app with my admin password.
    • Can’t fully customize the website location
    • Can’t customize the TLD (no example.co.dv to test an example.co.uk site, and yes I do that)
    • No menu-bar tool to activate/deactivate

    I’m going to keep using it, of course. The benefit of a fast spin-up of new sites, for what I do with testing people’s sites and fixing them at work, wins hands down.

  • Upcoming Speaking Gigs for DreamHost and WordPress

    header_logo

    • WordCamp San Francisco – July 26-29 (“Don’t Use WordPress Multisite”)
    • DreamCon – Aug 2-3 (“Choosing WordPress Plugins” and “Get Out Of The Monkey House”)
    • WordCamp Portland – Aug 10 (“Rolling Your WordPress Support Character (without any code)”)

    You get me twice at DreamCon. Twice the elf for one low payment!

    I believe all these will be recorded and made public.

  • Lightning Bolt at Portland!

    Lightning Bolt at Portland!

    2009Logo-NoText Surprise! I’m doing a lightning talk in Portland called “Rolling your WordPress Support Character (without any code)”

    A lightning talk is a magical 5 minute thrill ride with auto-advancing slides. 20 slides. 15 seconds a piece. And … GO!

    It’s not too late to buy tickets for WordCamp Portland. Bets are being taken as to what hat I’ll wear. (Remember, I’m a rogue, not a wizard, so no pointed hat.)

  • Evaluating Evil

    Evaluating Evil

    Credit: EvalBlog
    Credit: EvalBlog
    One of the things I do at DreamHost is help with hacked sites. This means when WP is hacked, I look at it, figure out how, and explain to the person how to fix it, or how to tell their tech folks what needs doing. There are occasions where I’ll delete things for them, but usually that happens when there’s a folder or file with weird permissions.

    We have a lot of tricks with what we look for, like base64, but recently I started to find files that missed my scan, but not my “Hey, wait, wp-mai1.php isn’t a WordPress file…” check. Files like this:

    $a51a0e6bb0e53a=str_rot13('tmhapbzcerff');$a51a0e6bb0e5e4=str_rot13(strrev('rqbp rq_46rfno'));
    eval($a51a0e6bb0e53a($a51a0e6bb0e5e4('eF6dWFtv6kYQ/it9qMQ5UlWBCVGtKg+JWozQaSrcU9+qKvIlBIGh6BBCyK/[...]')));
    eval($a51a0e6bb0e53a($a51a0e6bb0e5e4('eF7tW1uvotqW/ivnYSe1d85JignSvcxJPXgDtQCXKNdO50TAJSqop7xw6fR/7zHm1CUqqGW91EMnK1FgzHG/[...]')));
    

    Now obviously I can just add str_rot13 to my checklist (nothing in WordPress core uses it), but .. how do I look for those eval strings?

    Eval is a funny thing. In JavaScript: The Good Parts, Douglas Crockford states “eval is Evil: The eval function is the most misused feature of JavaScript. Avoid it” but he’s taking JS and I’m looking at php files. So with the (current) assumption that I can ignore js I can try this(I also use ack for this half the time, depends on my mood)(You can leave out ‘exclude SVN’ stuff if you want to. Most users don’t have it.):

    grep -R --exclude-dir="\.svn" --exclude="*.js" "eval" .
    

    That gets me a lot of files, though, and I don’t want to parse what I don’t need to. By the way, there’s one and only one file in all of WP that uses eval() in a ‘nefarious’ way, and that’s ./wp-admin/js/revisions-js.php, which is the WordPress easter egg. That’s also the only place you’ll see p,a,c,k,e,r code. But clearly I want to look for eval( or even eval($ because that’s more exact, and that should give me a better result.

    This is a two edged sword, of course. If I’m too precise, I will miss some of their shenanigans. If I’m not close enough to what I’m looking for, I get too much. And worst of all, I don’t always know what I’m looking for. Quite a lot of finding new hacks is a world where “I’ll know it when I see it.” So let’s take it down and say I want to find no JS, nothing in .svn, and anything with eval and a paren:

    grep -R --exclude-dir="\.svn" --exclude="*.js" -e 'eval(' .
    

    That’s a lot better, and in fact, this is a good start! But it’s hard to read because of how long the lines are:

    ./foo.php:eval($a51a0e6bb0e53a($a51a0e6bb0e5e4('eF6dWFtv6kYQ/it9qMQ5UlWBCVGtKg+JWozQaSrcU9
    ./foo.php:eval($a51a0e6bb0e53a($a51a0e6bb0e5e4('eF7tW1uvotqW/ivnYSe1d85JignSvcxJPXgDtQCXKN
    ./wp-admin/includes/class-pclzip.php://      eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);');
    ./wp-admin/js/revisions-js.php:eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('6(4(){2 e=6(\\'#Q\\').v();2 i=\\'\\\\\\',.R/=\\\\\\\\S-;T"<>U?+|V:W[]X{}\\'.u(\\'\\');2 o=\\'Y[]\\\\\\\\Z;\\\\\\'10,./11{}|12:"13<>?-=14+\\'.u(\\'\\');2 5=4(s){r=\\'\\';6.15(s.u(\\'\\'),4(){2 t=16.D();2 c=6.17(t,i);r+=\\'\$\\'==t?n:(-1==c?t:o[c])});j r};2 a=[\\'O.E[18 e.y.19.1a\\',\\'1b 1c. 1d .1e.,1f 1g\\',\\'O.E e.1h 1i 8\\',\\'9\\',\\'0\\'];2 b=[\\'<1j. 1k \$1l\\',\\'1m. 1n 1o 1p\\',\\'1q, 1r. ,1s. 1t\\'&#93;;2 w=&#91;&#93;;2 h=6(5(\\'#1u\\'));6(5(\\'1v\\')).1w(4(e){7(1x!==e.1y){j}7(x&amp;&amp;x.F){x.F();j G}1z.1A=6(5(\\'#1B\\')).1C(\\'1D\\');j G});2 k=4(){2 l=a.H();7(\\'I\\'==J l){7(m){2 c={};c&#91;5(\\'1E\\')&#93;=5(\\'1F\\');c&#91;5(\\'1G\\')&#93;=5(\\'1H..b\\');6(5(\\'1I 1J\\')).1K(c);p();h.v().1L({1M:1},z,\\'1N\\',4(){h.K()});d(m,L)}j}w=5(l).u(\\'\\');A()};2 A=4(){B=w.H();7(\\'I\\'==J B){7(m){h.M(5(\\'1O 1P\\'));d(k,C)}N{7(a.P){d(p,C);d(k,z)}N{d(4(){p();h.v()},C);d(4(){e.K()},L)}}j}h.M(B.D());d(A,1Q)};2 m=4(){a=b;m=1R;k()};p=4(){2 f=6(\\'p\\').1S(0);2 g=6.1T(f.q).1U();1V(2 g=f.q.P;g>0;g--){7(3==f.q[g-1].1W||\\'1X\\'==f.q[g-1].1Y.1Z()){f.20(f.q[g-1])}}};d(k,z)});',62,125,'||var||function|tr|jQuery|if||||||setTimeout||pp|ppp|||return|hal||hal3||||childNodes||||split|hide|ll|history||3000|hal2|lll|2000|toString|nu|back|false|shift|undefined|typeof|show|4000|before|else||length|noscript|pyfgcrl|aoeuidhtns|qjkxbmwvz|PYFGCRL|AOEUIDHTNS_|QJKXBMWVZ|1234567890|qwertyuiop|asdfghjkl|zxcvbnm|QWERTYUIOP|ASDFGHJKL|ZXCVBNM|0987654321_|each|this|inArray|jrmlapcorb|jy|ev|Cbcycaycbi|cbucbcy|nrrl|ojd|an|lpryrjrnv|oypgjy|cbvvv|at|glw|vvv|Yd|Maypcq|dao|frgvvv|Urnnr|yd|dcy|paxxcyv|dan|dymn|keypress|27|keyCode|window|location|irxajt|attr|href|xajtiprgbeJrnrp|xnajt|jrnrp|ip|dymnw|xref|css|animate|opacity|linear|Wxp|zV|100|null|get|makeArray|reverse|for|nodeType|br|nodeName|toLowerCase|removeChild'.split('|'),0,{}))
    ./wp-admin/press-this.php:		var my_src = eval(
    ./wp-admin/press-this.php:			var my_src = eval(
    ./wp-admin/press-this.php:							eval(data);
    ./wp-includes/class-json.php: * Javascript, and can be directly eval()'ed with no further parsing
    ./wp-includes/functions.php:		if ( doubleval($bytes) >= $mag )
    

    Okay, lets get smarter!

    grep -R --exclude-dir="\.svn" --exclude="*.js" -e 'eval(' .|cut -c -80
    

    Now I’m telling it to cut up after 80 characters, because it’s easier to pick out the bad with just that much. Look:

    ./foo.php:eval($a51a0e6bb0e53a($a51a0e6bb0e5e4('eF6dWFtv6kYQ/it9qMQ5UlWBCVGtKg+J
    ./foo.php:eval($a51a0e6bb0e53a($a51a0e6bb0e5e4('eF7tW1uvotqW/ivnYSe1d85JignSvcxJ
    ./wp-admin/includes/class-pclzip.php://      eval('$v_result = '.$p_options[PCLZ
    ./wp-admin/js/revisions-js.php:eval(function(p,a,c,k,e,r){e=function(c){return(c
    ./wp-admin/press-this.php:		var my_src = eval(
    ./wp-admin/press-this.php:			var my_src = eval(
    ./wp-admin/press-this.php:							eval(data);
    ./wp-includes/class-json.php: * Javascript, and can be directly eval()'ed with n
    ./wp-includes/functions.php:		if ( doubleval($bytes) >= $mag )
    

    Part of the reason this works is I know what I’m looking for. WordPress, in general, doesn’t encrypt content. Passwords and security stuff, yes, but when it does that, it uses variables so you would get eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);');, which remains totally human readable. By that I mean I can see clear words that are easy to search for in a doc, or via grep or awk without being forced to copy/paste. I can remember “PCLZIP underscore CB…”

    RandomCharacters_320Those random characters are not human readable at all. That’s how I know they’re bad. Of course, if someone got clever-er, they would start naming those variables things that ‘make sense’ in the world of WP, and I have a constant fear that by pointing out how I can tell this is a hack, I give them ideas on how to do evil-er things to us.

    It’s for reasons like this that I, when faced with a hack or asked to clean one up, always perform Scorched Earth Security. I delete everything and reinstall it. I look for PHP and JS files in wp-content/uploads, or .htaccess files anywhere they shouldn’t be (in clean WP, you have two at most: at the root of your site and in akismet). I make sure I download my themes and plugins from known clean locations. I’m careful. And I always change my passwords. Heck, I don’t even know what mine are right now!

    But none of this is static enough for me to say “This is the fix forever and ever” or “this is how you will always find the evil…” By the time we’ve codified and discussed best methods, the hackers have moved on. The logic of what to look for now may not last long, but the basic concept of looking for wrong and how to search for it should remain a good starting point for a while yet.

    Do you have special tricks you use to find the evil? Like what Topher did to clean up a hack?