[[ShellHallOfShame|<- Page 1: /usr/bin/lpr]] | [[ShellHallOfShame/Page3|Page 3: /usr/bin/read ->]] == /usr/dt/bin/Xsession == {{{#!highlight sh #!/usr/bin/ksh # ########################################################################## # # Xsession # # Common Desktop Environment (CDE) # # Configuration script for the Login Manager # # (c) Copyright 1996 Digital Equipment Corporation. # (c) Copyright 1993,1994,1996 Hewlett-Packard Company. # (c) Copyright 1993,1994,1996 International Business Machines Corp. # (c) Copyright 1993,1994,1996 Sun Microsystems, Inc. # (c) Copyright 1993,1994,1996 Novell, Inc. # (c) Copyright 1996 FUJITSU LIMITED. # (c) Copyright 1996 Hitachi. # # ************** DO NOT EDIT THIS FILE ************** # # /usr/dt/bin/Xsession is a factory-default file and will # be unconditionally overwritten upon subsequent installation. # Modification is discouraged. # # $XConsortium: Xsession.src /main/20 1997/11/25 14:16:29 narayan $ # # Usage: $0 [-session session_name] # # ########################################################################## # 112 # ########################################################################## # # # This script starts the user's session. It searches for one of three # types of startup mechanisms, in the following order: # # DT existence of CDE DT Session Manager on the system # XDM "$HOME/.xsession" (executable) # xinit "$HOME/.x11start" (executable) # # If none of these startup mechanisms exist, a default window manager # and terminal emulator client are started. # # ########################################################################## # # Variables must be explicitly exported # set +a # ########################################################################## # # Initialize session startup logging # # ########################################################################## exec >/dev/null 2>/dev/null # 147 LOGDIR=$HOME/.dt LOGFILENAME=$LOGDIR/startlog MSGLOGFILENAME=$LOGDIR/errorlog if [ ! -d $LOGDIR ]; then mkdir $LOGDIR if [ -d $LOGDIR ]; then /usr/bin/chmod 755 $LOGDIR fi fi [ -f $LOGFILENAME.older ] && rm -f $LOGFILENAME.older [ -f $LOGFILENAME.old ] && mv -f $LOGFILENAME.old $LOGFILENAME.older [ -f $LOGFILENAME ] && mv -f $LOGFILENAME $LOGFILENAME.old touch $LOGFILENAME [ ! -f $MSGLOGFILENAME ] && touch $MSGLOGFILENAME if [ -w $LOGFILENAME ]; then exec >>$LOGFILENAME 2>&1 fi Log() { echo "--- $1" >>$LOGFILENAME 2>&1 } Log "$(date)" Log "$0 starting..." # ########################################################################## # # Global environment section # # DT pre-sets the following environment variables for each user. # # (internal) # # DISPLAY set to the value of the first field in the Xservers file. # HOME set to the user's home directory (from /etc/passwd) # LANG set to the display's current NLS language (if any) # LC_ALL set to the value of $LANG # LOGNAME set to the user name # PATH set to the value of the Dtlogin "userPath" resource # USER set to the user name # SHELL set to the user's default shell (from /etc/passwd) # TZ set to the value of the Dtlogin "timeZone" resource # # # (Xsession) # # TERM set to xterm # EDITOR set to the default editor # KBD_LANG set to the value of $LANG for certain languages # MAIL set to "/var/mail/$USER" # # # Three methods are available to modify or add to this list depending # on the desired scope of the resulting environment variable. # # 1. X server and/or all users on a display (Xconfig file) # 2. all users on a display (Xsession file) # 3. individual users (.dtprofile file) # # See DT on-line help, the DT Users Guide, or the Dtlogin(1X) man # page for details on setting environment variables. # # # ########################################################################## [ -z "$EDITOR" ] && EDITOR=/usr/dt/bin/dtpad [ -z "$MAIL" ] && MAIL="/var/mail/$USER" [ -z "$LANG" ] && LANG="C" TERM=dtterm SESSION_SVR=`hostname` export PATH EDITOR MAIL TERM SESSION_SVR LANG # 256 # # Set the keyboard language if necessary... # if [ ! -z "$LANG" ] then case $LANG in bulgarian | czech | hebrew | hungarian | \ japanese | korean | polish | rumanian | \ russian | serbocroatian) KBD_LANG=$LANG export KBD_LANG;; chinese-t) KBD_LANG=t_chinese export KBD_LANG;; chinese-s) KBD_LANG=s_chinese export KBD_LANG;; *);; esac fi # # Locate configuration file directories # XDIR="/usr/bin/X11" DT_BINPATH=/usr/dt/bin DT_INSTALL_CONFIG=/usr/dt/config DT_CONFIG=/etc/dt/config DT_CONFIG_PATH="$DT_CONFIG $DT_INSTALL_CONFIG" # ########################################################################## # # Default desktop component configuration variable settings # # This section sets the default value for variables controlling # some desktop components. # # ########################################################################## # # Input method server startup # if [ -z "$DTSTARTIMS" ]; then DTSTARTIMS=True fi if [ "$DTSTARTIMS" = "False" ]; then unset DTSTARTIMS fi # # Default desktop screen saver action list # export DTSCREENSAVERLIST="StartDtscreenSwarm StartDtscreenQix \ StartDtscreenFlame StartDtscreenHop StartDtscreenImage StartDtscreenLife \ StartDtscreenRotor StartDtscreenPyro StartDtscreenWorm StartDtscreenBlank" # # Session startup clients and args # if [ "$SESSIONTYPE" = "altDt" ]; then dtstart_session[0]="$SDT_ALT_SESSION" dtstart_hello[0]="$SDT_ALT_HELLO" dtstart_hello_info[0]="$SDT_ALT_HELLO" else DTSESSION_ARGS="" if [ $# -ge 2 ]; then if [ "$1" = "-session" ]; then DTSESSION_ARGS="$1 $2" fi fi dtstart_session[0]="$DT_BINPATH/dtsession $DTSESSION_ARGS" dtstart_hello[0]="$DT_BINPATH/dthello &" dtstart_hello_info[0]="$DT_BINPATH/dthello -file $INFO_PATH -file /etc/copyright &" fi dtstart_session[1]="$HOME/.xsession" dtstart_session[2]="$HOME/.x11start" dtstart_session[3]="$XDIR/xterm -geometry 80x24+10+10" dtstart_hello[1]="$XDIR/xsetroot -default &" dtstart_searchpath="$DT_BINPATH/dtsearchpath -ksh" dtstart_ttsession="$DT_BINPATH/ttsession -s" dtstart_dtdbcache="$DT_BINPATH/dtdbcache -init" # # dtdbcache file's directory should match # _DTDTSMMTEMPDIR in DtSvc/DtUtil1/DtsMM.h # dtdbcacherm="rm -f /tmp/dtdbcache_$DISPLAY" dtstart_appgather="$DT_BINPATH/dtappgather &" xdmstart_session[0]="$HOME/.xsession" xdmstart_session[1]="/usr/lib/X11/xdm/sys.xsession" xdmstart_session[2]="$XDIR/xterm -geometry 80x24+10+10 -ls" xdmstart_hello="$XDIR/xsetroot -default &" SESSIONLOGDIR=$LOGDIR/sessionlogs SESSIONLOGFILENAME="$SESSIONLOGDIR/$SESSION_SVR"_DISPLAY=$DISPLAY if [ ! -d $SESSIONLOGDIR ]; then mkdir $SESSIONLOGDIR if [ -d $SESSIONLOGDIR ]; then /usr/bin/chmod 755 $SESSIONLOGDIR fi fi touch $SESSIONLOGFILENAME if [ -w $SESSIONLOGFILENAME ]; then dtstart_sessionlogfile="$SESSIONLOGFILENAME" else dtstart_sessionlogfile="/dev/null" fi rm -f $SESSIONLOGFILENAME # 394 # 30 # # Determine Xsession parent # pexec=$(LC_TIME=C /usr/bin/ps -p $PPID | awk 'NR==2 {print $4}') Log "Xsession started by $pexec" # ########################################################################## # # Append desktop font aliases to font path # # ########################################################################## # 66 if [ "${pexec##*/}" != "dtlogin" ]; then # # If Xsession launched by dtlogin, it is assumed that the desktop # font path has already been added by Xsetup, so no need to add it here. # # 92 # # Append desktop font paths. Note: these directories should be # accessable by the X server. The file precedence is: # # /etc/dt/config/xfonts/C # /usr/dt/config/xfonts/C # /etc/dt/config/xfonts/$LANG # /usr/dt/config/xfonts/$LANG # Log "setting font path..." if [ "$DTXSERVERLOCATION" != "remote" ]; then # # Since X server is local, optimize by checking local desktop # font directories and making one call to xset. # if [ -f /etc/dt/config/xfonts/C/fonts.dir ]; then fontpath=/etc/dt/config/xfonts/C fi if [ -f /usr/dt/config/xfonts/C/fonts.dir ]; then if [ -z "$fontpath" ]; then fontpath=/usr/dt/config/xfonts/C else fontpath=$fontpath,/usr/dt/config/xfonts/C fi fi if [ "$LANG" != "C" ]; then if [ -f /etc/dt/config/xfonts/$LANG/fonts.dir ]; then if [ -z "$fontpath" ]; then fontpath=/etc/dt/config/xfonts/$LANG else fontpath=$fontpath,/etc/dt/config/xfonts/$LANG fi fi fi if [ "$LANG" != "C" ]; then if [ -f /usr/dt/config/xfonts/$LANG/fonts.dir ]; then if [ -z "$fontpath" ]; then fontpath=/usr/dt/config/xfonts/$LANG else fontpath=$fontpath,/usr/dt/config/xfonts/$LANG fi fi fi if [ ! -z "$fontpath" ]; then $XDIR/xset fp+ $fontpath fi fontpath=/usr/lib/X11/fonts/iso_8859.15/75dpi if [ ! -z "$fontpath" ]; then $XDIR/xset fp+ $fontpath fi else # # Since X server not local, we don't know if the desktop font # directories exist on the X server machine, so we have to # set them one at a time. # $XDIR/xset fp+ /etc/dt/config/xfonts/C 1>/dev/null $XDIR/xset fp+ /usr/dt/config/xfonts/C 1>/dev/null if [ "$LANG" != "C" ]; then $XDIR/xset fp+ /etc/dt/config/xfonts/$LANG 1>/dev/null fi if [ "$LANG" != "C" ]; then $XDIR/xset fp+ /usr/dt/config/xfonts/$LANG 1>/dev/null fi fontpath=/usr/lib/X11/fonts/iso_8859.15/75dpi if [ ! -z "$fontpath" ]; then $XDIR/xset fp+ $fontpath fi fi fi # 486 # 407 # ########################################################################## # # Source user's desktop profile # # This section determines if the user has a desktop profile in their # home directory. If not, the desktop default profile is copied to # the home directory. The desktop profile is then sourced. The purpose # is to incorporate any per-user/per-session environment customizations # and thereby propagate them to applications and desktop components. # # ########################################################################## DTSYSPROFILE=sys.dtprofile DTPROFILE=.dtprofile if [ ! -f $HOME/$DTPROFILE ]; then for i in $DT_CONFIG_PATH do if [ -f $i/$DTSYSPROFILE ]; then /usr/bin/awk ' BEGIN {printit=1} /SYSPROFILE COMMENT START/ {printit=0; next} /SYSPROFILE COMMENT END/ {printit=1; next} printit==1 {print}' <$i/$DTSYSPROFILE >$HOME/$DTPROFILE /usr/bin/chmod 755 $HOME/$DTPROFILE break fi done fi # # source the .dtprofile. # if [ -f $HOME/$DTPROFILE ]; then Log "sourcing $HOME/$DTPROFILE..." . $HOME/$DTPROFILE fi # ########################################################################## # # External Xsession processing section # # This section searches the Xsession.d subdirectory and sources # the files contained therein. The purpose is to set up any # per-user/per-session environment customizations and thereby propagate # them to applications and desktop components. # # ########################################################################## DT_XSESSION_DIR=Xsession.d # # Run custom Xsession scripts for this session. # If multiple scripts have the same name, run only the first one found. # for SCRIPT in $( ( for i in $DT_CONFIG_PATH; do if [[ -d $i/$DT_XSESSION_DIR ]]; then ls $i/$DT_XSESSION_DIR fi done ) | sort -u ); do for i in $DT_CONFIG_PATH do if [ -x $i/$DT_XSESSION_DIR/$SCRIPT -a \ \( ! -d $i/$DT_XSESSION_DIR/$SCRIPT \) ]; then Log "sourcing $i/$DT_XSESSION_DIR/$SCRIPT..." . $i/$DT_XSESSION_DIR/$SCRIPT break fi done done # ########################################################################## # # Startup section. # # Note: The ksh syntax ${parameter%% *} is used when appropriate to # remove any command line options that may have been included # in the definition of a DT executable below. # # ########################################################################## # # Return first command in array named by $1 that is executable # GetFirst() { let i=0 while true; do eval "cmd=\${$1[$i]}" [ -z "$cmd" ] && break [ -x "${cmd%% *}" ] && echo "$cmd" && break Log "could not start $cmd" let i=$i+1 done } # # Start first command in array named by $1 that is executable. If # $2 is 'eval', command result will be 'eval'ed. # StartFirst() { first=$(GetFirst $1) if [ ! -z "$first" ]; then Log "starting $first" if [ "$2" = "eval" ]; then eval `eval "PATH=$DT_BINPATH:$PATH $first"` else eval "PATH=$DT_BINPATH:$PATH $first" fi fi } # # Prepare for session startup # if [ "$DTSOURCEPROFILE" = "true" ] then case ${SHELL##*/} in sh | ksh | dtksh) shellprofile="$HOME/.profile";; bash) shellprofile="$HOME/.bash_profile";; csh | tcsh) shellprofile="$HOME/.login";; *) Log "non-standard shell $SHELL" esac fi if [ "$shellprofile" -a ! -f "$shellprofile" ] then Log "could not read $shellprofile" unset shellprofile fi if [ "$SESSIONTYPE" = "xdm" ]; then startup=$(GetFirst xdmstart_session) # get xdm session client StartFirst xdmstart_hello # start xdm hello client else startup=$(GetFirst dtstart_session) # get desktop session client if [ -n "$INFO_PATH" ]; then StartFirst dtstart_hello_info # start desktop hello client else StartFirst dtstart_hello # start desktop hello client fi StartFirst dtstart_searchpath eval # setup desktop search paths tooltalk=$(GetFirst dtstart_ttsession) # get tooltalk client dtdbcache=$(GetFirst dtstart_dtdbcache) # get dtdbcache client # 575 StartFirst dtstart_appgather # setup session applications fi # # Start the session. # if [ $shellprofile ]; then Log "execing $startup using $shellprofile..." source_profile=". $shellprofile" source_login="source $shellprofile" else Log "execing $startup..." source_profile="echo 'not sourcing $HOME/.profile (see $HOME/.dtprofile)'" source_login="echo 'not sourcing $HOME/.login (see $HOME/.dtprofile)'" fi if [ -z "$dtdbcache" ]; then dtdbcache="echo could not start $dtstart_dtdbcache" fi export DT=true; case ${SHELL##*/} in sh | bash) $SHELL -c "$source_profile; \ unset DT; \ ############################################################################ # Exiting from the script to return back to dtgreet screen in case dtdbcache # fails to initilaize Action database # ########################################################################## $dtdbcache && \ (PATH=/usr/dt/bin:\$PATH $tooltalk; \ $startup > $dtstart_sessionlogfile 2>&1)" ;; ksh | dtksh) $SHELL -c "$source_profile; \ unset DT; \ $dtdbcache && \ (PATH=/usr/dt/bin:\$PATH $tooltalk;\ $startup >| $dtstart_sessionlogfile 2>&1)" ;; csh | tcsh) $SHELL -c "unsetenv _ PWD; \ $source_login; \ unsetenv DT; \ $dtdbcache && \ ( (set path = ( $DT_BINPATH \$path ); $tooltalk ); \ $startup >&! $dtstart_sessionlogfile)" ;; *) unset DT $dtdbcache || exit StartFirst dtstart_ttsession $startup >| $dtstart_sessionlogfile 2>&1 ;; esac $dtdbcacherm # remove the actions/datatypes cachefile # #################### eof ################################# }}} Are you frightened yet? You should be. The only thing saving this script from being a ''massive'' security hole is the fact that it's run without any special privileges. The huge comment blocks are typical, and are not the worst I've seen. The indenting is not bad, though I dislike how the not-inside-function stuff is still indented two spaces. I don't understand what the '''# 112''' (etc.) comments are supposed to mean, either. Perhaps they correspond to line numbers in some other file, from which this script is produced automatically? I don't know. The first problem we see is a lack of [[Quotes]]. {{{ LOGDIR=$HOME/.dt LOGFILENAME=$LOGDIR/startlog [ -f $LOGFILENAME ] && mv -f $LOGFILENAME $LOGFILENAME.old }}} What happens here in the unlikely event that `HOME` contains spaces? Nothing good, to be sure. And it would be so easy to fix that. They also [[ParsingLs|parse the output of ls]], here: {{{ for SCRIPT in $( ( for i in $DT_CONFIG_PATH; do if [[ -d $i/$DT_XSESSION_DIR ]]; then ls $i/$DT_XSESSION_DIR fi done ) | sort -u ); do for i in $DT_CONFIG_PATH do ... }}} Yikes! What a disaster. Looks like they're taking a list of directory names, using `ls` to enumerate the contents of each one, shoving it all through `sort -u`, then WordSplitting the result of that and hoping it's still filenames. Well, sure, if you control all the files on the system (or at least in the directories pointed to by `$DT_CONFIG_PATH`) then you might be able to assume none of them contain spaces. But still, was this really the best approach they could think of? How about this instead? {{{ for dir in "${DT_CONFIG_PATH[@]}"; do for SCRIPT in "$dir"/*; do [[ -x $SCRIPT ]] || continue ... run it ... done done }}} Granted, that doesn't eliminate duplicates (the way their `sort -u` does). Though I still think their requirement {{{ # If multiple scripts have the same name, run only the first one found. }}} is pretty daft. Is there a simple way to remove duplicates without having associative arrays available (and this system has ksh88, not ksh93, so they aren't)? Well, not exactly. Let's take one step back and see what they're [[XyProblem|really doing]]: {{{ DT_INSTALL_CONFIG=/usr/dt/config DT_CONFIG=/etc/dt/config DT_CONFIG_PATH="$DT_CONFIG $DT_INSTALL_CONFIG" for SCRIPT in $( ( for i in $DT_CONFIG_PATH; do ... ) | sort -u ); do for i in $DT_CONFIG_PATH do if [ -x $i/$DT_XSESSION_DIR/$SCRIPT -a \ \( ! -d $i/$DT_XSESSION_DIR/$SCRIPT \) ]; then Log "sourcing $i/$DT_XSESSION_DIR/$SCRIPT..." . $i/$DT_XSESSION_DIR/$SCRIPT break fi done done }}} I've omitted the distracting "parsing ls" part here, and included the variables from hundreds of lines earlier so we can see what they're trying to accomplish. They've actually got two directories: the sysadmin's customized one (`/etc/dt/config`) and the factory default one (`/usr/dt/config`). The sysadmin is supposed to copy files from the latter to the former, and modify them. So, they want to run all the scripts from `/etc/dt/config` and all the scripts from `/usr/dt/config` that weren't present in `/etc/dt/config`. Given '''that''' explanation, the task becomes so much clearer. Start by ''not'' merging the two lists. Keep them separate. {{{ for script in "$DT_CONFIG"/*; do ... run it ... done for script in "$DT_INSTALL_CONFIG"/*; do basename=${script##*/} [[ -x $DT_CONFIG/$basename ]] && continue ... run it ... done }}} There! Isn't that much cleaner? Merging the two lists together simply muddied everything to the point that they probably couldn't see the ''original'' problem any more. They were stuck with this strange new problem to solve (removing duplicates from a merged list), but it was completely unnecessary. But the part of this script that truly terrified me was this: {{{ # Start first command in array named by $1 that is executable. If # $2 is 'eval', command result will be 'eval'ed. # StartFirst() { first=$(GetFirst $1) if [ ! -z "$first" ]; then Log "starting $first" if [ "$2" = "eval" ]; then eval `eval "PATH=$DT_BINPATH:$PATH $first"` else eval "PATH=$DT_BINPATH:$PATH $first" fi fi } }}} Not just one, but '''two''' [[BashFAQ/048|eval commands]] at the same time! Holy shit! What on earth prompted them to write ''that''? Let's see... this function is used from here: {{{ if [ "$SESSIONTYPE" = "xdm" ]; then startup=$(GetFirst xdmstart_session) # get xdm session client StartFirst xdmstart_hello # start xdm hello client }}} And we clearly need to see the `GetFirst` function too: {{{ # Return first command in array named by $1 that is executable # GetFirst() { let i=0 while true; do eval "cmd=\${$1[$i]}" [ -z "$cmd" ] && break [ -x "${cmd%% *}" ] && echo "$cmd" && break Log "could not start $cmd" let i=$i+1 done } }}} (More `eval` voodoo!) And here's what they're using for input to all this: {{{ xdmstart_session[0]="$HOME/.xsession" xdmstart_session[1]="/usr/lib/X11/xdm/sys.xsession" xdmstart_session[2]="$XDIR/xterm -geometry 80x24+10+10 -ls" xdmstart_hello="$XDIR/xsetroot -default &" }}} So... clearly their code design is driven by this data structure. They've got a bunch of [[BashFAQ/050|commands inside variables]]. Then, later, they iterate through the array of commands, try to see which ones are executable, and then execute the first one that qualifies. Is this complexity really needed? They don't use this `xdmstart_session` array anywhere else. It's never modified in any other part of this program. In theory, it could be modified by the user's `$HOME/$DTPROFILE` which is dotted in before this array is used; or by one of the scripts that are dotted in by the whole `$DT_CONFIG` section we discussed a moment ago. Whether that's something they wanted to allow, I can't say with certainty. I'm ''fairly'' sure they didn't intend for the end user to be overriding that array in their `~/.dtprofile` file, but it's not stated either way. If they didn't need that sort of complexity, then they could eliminate that array altogether. Just do this: {{{ if [ "$SESSIONTYPE" = "xdm" ]; then if [[ -x $HOME/.xsession ]]; then PATH="$DT_BINPATH:$PATH" "$HOME"/.xsession elif [[ -x /usr/lib/X11/xdm/sys.xsession ]]; then PATH="$DT_BINPATH:$PATH" /usr/lib/X11/xdm/sys.xsession elif [[ -x $XDIR/xterm ]]; then PATH="$DT_BINPATH:$PATH" "$XDIR"/xterm -geometry 80x24+10+10 -ls fi }}} Of course, they aren't actually running the command immediately. They're storing the command in ''yet another'' variable, then doing a whole bunch of manipulation, and finally handing it to a `$SHELL -c` to evaluate. But the same principle could be used there. To be fair, however, the `GetFirst`/`StartFirst` pair is used on another array that's built dynamically: {{{ if [ "$SESSIONTYPE" = "altDt" ]; then dtstart_session[0]="$SDT_ALT_SESSION" ... else ... dtstart_session[0]="$DT_BINPATH/dtsession $DTSESSION_ARGS" fi dtstart_session[1]="$HOME/.xsession" dtstart_session[2]="$HOME/.x11start" dtstart_session[3]="$XDIR/xterm -geometry 80x24+10+10" ... else startup=$(GetFirst dtstart_session) # get desktop session client }}} That was probably the inspiration for the whole "let's build an array of commands, then search through it for one that's executable later on, and then eval it" approach. It's an ugly solution to an ugly problem. It's even uglier when we look at this bit: {{{ DTSESSION_ARGS="" if [ $# -ge 2 ]; then if [ "$1" = "-session" ]; then DTSESSION_ARGS="$1 $2" fi fi dtstart_session[0]="$DT_BINPATH/dtsession $DTSESSION_ARGS" }}} They've smashed the positional parameters together into a string. If the user supplied an argument that contains spaces (or [[glob]] characters, or is an empty string), this breaks. Normally the solution to ''that'' problem would be to use an array. And at first glance, you might think "Well, hell, just put the user's arguments into a second array, and run `"$command" "${extra_args[@]}"`". That would work if it weren't for the whole `$SHELL -c` layer that comes later. They've made everything so complex that you have to destroy it completely before you can rebuild it. That leaves us staring open-mouthed at the `StartFirst` function and its double `eval`. This page has already gone on long enough, though. At some point you just have to back away... slowly... without making any sudden movements. [[ShellHallOfShame|<- Page 1: /usr/bin/lpr]] | [[ShellHallOfShame/Page3|Page 3: /usr/bin/read ->]]