Oct 252011
 

(foreword: you can look at the code on github if you like that better)

whilst waiting for what seems like forever for some files to transfer, i decided to brush up on my bash scripting abilities. i’ve always wanted to create an ssh-based mud (multi-user dungeon) before, and the ~5 hours this transfer is going to take should be quite long enough.

i started off with just a new user with some tweaks and prep:

# useradd -m -s/bin/mud muduser
# passwd muduser
# mkdir /home/muduser/world /home/muduser/players
# chown muduser:muduser /home/muduser -R

after that i got down to business. since the “shell” will be /bin/mud, i created that file:

#!/bin/bash
echo "Welcome to slowbro's MUD thing"
echo -ne "What is your username?\\n> "
read name

nothing too spectacular. i immediately thought about all the people who would want to make names with spaces and weird characters, shuddered, and then added some checking code:

#!/bin/bash
i=0
user=""
echo "Welcome to slowbro's MUD thing"
echo -ne "What is your username?\\n> "
read name
namepass=0;
while [[ "$namepass" == "0" ]];do
    if [[ "$i" -gt "1" ]];then
        echo "Fine, be that way."
        exit
    fi  
    if [[ "$name" == "" ]];then
        if [[ "$i" == "0" ]];then
            echo -en "Hey, quit fooling around! Give me a name.\\n> "
        else
            echo -en "Seriously, last warning: what is your name?\\n> "
        fi
        read name
    else
        if [[ "$name" =~ "^[a-zA-Z0-9]{1,64}$" ]];then
            break
        else
            echo -en "Sorry, that name is invalid. A-z and 0-9 only, max 64 characters!\\n> "
            read name
        fi
    fi
    i=$i+1
done

at this point i started to feel like i was repeating myself a lot. functions, anypony? i’m always a fan of code re-use, so i made some handy functions and tacked them on top.

#!/bin/bash

function msg(){
    echo -e "$1"
}

function prompt(){
    echo -n "> "
    read $1
}

function prompt_secret(){
    echo -n "> "
    read -s $1
    echo ""
}

function prompt_regex(){
    while [ 1=1 ];do
        prompt $1
        val=`eval echo "\\\\$$1"`
        if [[ "$val" =~ $2 ]];then
            break;
        else
            msg "Sorry, input was invalid: $3"
        fi;
    done
}

function prompt_al(){ 
    prompt_regex $1 "^[A-Za-z]{1,}$" "A-z only!"
}

function prompt_alnum(){
    prompt_regex $1 "^[A-Za-z0-9]{1,}$" "A-z and 0-9 only!"
}

msg "Welcome to slowbro's MUD thing"
msg "What is your username?"
prompt_alnum name

msg "What would you like to do? Type 'help' for help, or 'exit' to exit."

that makes the console a bit less impersonal, but ends up saving a lot of headache in the future. i added a few more functions to handle users logging in and new users:

function login(){
    if [[ -f "$HOME/players/$1" ]];then
        userfile=`cat $HOME/players/$1`
        user_pass=`echo "$userfile" | grep ^password | cut -d'=' -f2`
        user_fname=`echo "$userfile" | grep ^fname | cut -d'=' -f2`
        user_lname=`echo "$userfile" | grep ^lname | cut -d'=' -f2`
        user_x=`echo "$userfile" | grep ^pos_x | cut -d'=' -f2`
        user_y=`echo "$userfile" | grep ^pos_y | cut -d'=' -f2`
        user_lastlog=`echo "$userfile" | grep ^lastlog | cut -d'=' -f2`
        checklogin $1
    else
        msg "\\nWelcome to the MUD, $1! This looks like the first time I've seen you."
        newuser $1
    fi
}

function checklogin(){
    msg "Password, please."
    prompt_secret pass
    if [[ `echo $pass | sha1sum | cut -d' ' -f1` != $user_pass ]];then
        msg "Sorry, that's not correct."
        exit
    else
        msg "Welcome back, $1! I last saw you `date -d @$user_lastlog`."
        sed -i "s/lastlog=$user_lastlog/lastlog=`date +%s | tr -d '\\n'`/" "$HOME/players/$1"
    fi
}

function newuser(){
    msg "I need to collect some info about you, for posterity.\\nHow about a password?"
    passfail=1
    while [[ "$passfail" == "1" ]];do
        prompt_secret pass1
        msg "And again?"
        prompt_secret pass2
        if [[ "$pass1" == "$pass2" && "$pass1" != "" ]];then
            break
        else
            msg "Sorry, those passwords don't seem to match (or were blank). Try again!"
        fi
    done
    msg "Great, what is your first name?"
    prompt_al fname
    msg "And your last?"
    prompt_al lname
    msg "Thanks, your name is $fname $lname!"
    pass_hash=`echo $pass1 | sha1sum | cut -d' ' -f1`
    touch "$HOME/players/$name"
    echo "\\
[$name]
password=$pass_hash
fname=$fname
lname=$lname
loc_x=0
loc_y=0
lastlog=`date +%s`
" > "$HOME/players/$name"
    msg "Okay, I have you down. be careful out there."
}

now that we have the methods, we need to have some logic. i use a while loop to keep the user in the console- if you let the script end it will disconnect them. my while loop + initial logic + related functions:

function showhelp(){
  case $1 in
    info)
        msg "info   : shows your player info. that's it."
    ;;

    set)
        msg "Sorry, no help is available for that yet."
    ;;

    exit)
        msg "exit  : exits you from the dungeon, believe it or not."
    ;;

    *)
    msg "\
Welcome to slowbro's MUD thing. There are a few commands you can use:

info    : show your player info.
set     : set certain things about your player. try typing 'help set' for more info.
help    : show this help. you can to 'help <item>' to get specific help about an item in this menu.
exit    : exit the dungeon.
"
  esac;
}

msg "Welcome to slowbro's MUD thing"
msg "What is your username?"
prompt_alnum name

login $name

msg "What would you like to do? Type 'help' for help, or 'exit' to exit."

while [ 1=1 ];do
    prompt in
    action=`echo $in | cut -d' ' -f1`
    rest=`echo $in | sed -e "s/$action //"`
    case $action in
        help)
            showhelp "$rest"
        ;;
        exit | bye | quit)
            msg "See ya!"
            exit
        ;;
        *)
            msg "Sorry, I don't know what that is. Try typing 'help'."
    esac
done

this allows for a few basic commands: ‘help’ and ‘exit’ (or bye/quit), and a catchall for unknown commands. i added a case to the showhelp function in order to support help for specific items.

that’s all I have done so far. i’ll update when it gets a bit better. you can see the code on github

some usage examples:

[slowbro@alta mud]$ ssh muduser@localhost
muduser@localhost's password: 
Last login: Tue Oct 25 17:17:07 2011 from 127.0.0.1
Welcome to slowbro's MUD thing
What is your username?
> slowbro2

Welcome to the MUD, slowbro2! This looks like the first time I've seen you.
I need to collect some info about you, for posterity.
How about a password?
> 
And again?
> 
Great, what is your first name?
> Slow
And your last?
> Bro
Thanks, your name is Slow Bro!
Okay, I have you down. be careful out there.
What would you like to do? Type 'help' for help, or 'exit' to exit.
> help
Welcome to slowbro's MUD thing. There are a few commands you can use:

info    : show your player info.
set     : set certain things about your player. try typing 'help set' for more info.
help    : show this help. you can to 'help <item>' to get specific help about an item in this menu.
exit    : exit the dungeon.

> exit
See ya!
Connection to localhost closed.

[slowbro@alta mud]$ ssh muduser@localhost
muduser@localhost's password: 
Last login: Tue Oct 25 17:43:48 2011 from 127.0.0.1
Welcome to slowbro's MUD thing
What is your username?
> slowbro2
Password, please.
> 
Welcome back, slowbro2! I last saw you Tue Oct 25 17:44:01 MST 2011.
What would you like to do? Type 'help' for help, or 'exit' to exit.
> bye
See ya!
Connection to localhost closed.