# -*-shell-script-*- # This should be sourced by bash (though we welcome changes to make it POSIX sh compliant) # Shared sh functions for the monkeysphere # # Written by # Jameson Rollins # Jamie McClelland # Daniel Kahn Gillmor # # Copyright 2008-2009, released under the GPL, version 3 or later # all-caps variables are meant to be user supplied (ie. from config # file) and are considered global ######################################################################## ### UTILITY FUNCTIONS # output version info version() { cat "${SYSSHAREDIR}/VERSION" } # failure function. exits with code 255, unless specified otherwise. failure() { [ "$1" ] && echo "$1" >&2 exit ${2:-'255'} } # write output to stderr based on specified LOG_LEVEL the first # parameter is the priority of the output, and everything else is what # is echoed to stderr. If there is nothing else, then output comes # from stdin, and is not prefaced by log prefix. log() { local priority local level local output local alllevels local found= # don't include SILENT in alllevels: it's handled separately # list in decreasing verbosity (all caps). # separate with $IFS explicitly, since we do some fancy footwork # elsewhere. alllevels="DEBUG${IFS}VERBOSE${IFS}INFO${IFS}ERROR" # translate lowers to uppers in global log level LOG_LEVEL=$(echo "$LOG_LEVEL" | tr "[:lower:]" "[:upper:]") # just go ahead and return if the log level is silent if [ "$LOG_LEVEL" = 'SILENT' ] ; then return fi for level in $alllevels ; do if [ "$LOG_LEVEL" = "$level" ] ; then found=true fi done if [ -z "$found" ] ; then # default to INFO: LOG_LEVEL=INFO fi # get priority from first parameter, translating all lower to # uppers priority=$(echo "$1" | tr "[:lower:]" "[:upper:]") shift # scan over available levels for level in $alllevels ; do # output if the log level matches, set output to true # this will output for all subsequent loops as well. if [ "$LOG_LEVEL" = "$level" ] ; then output=true fi if [ "$priority" = "$level" -a "$output" = 'true' ] ; then if [ "$1" ] ; then echo "$@" else cat fi | sed 's/^/'"${LOG_PREFIX}"'/' >&2 fi done } # run command as monkeysphere user su_monkeysphere_user() { # our main goal here is to run the given command as the the # monkeysphere user, but without prompting for any sort of # authentication. If this is not possible, we should just fail. # FIXME: our current implementation is overly restrictive, because # there may be some su PAM configurations that would allow su # "$MONKEYSPHERE_USER" -c "$@" to Just Work without prompting, # allowing specific users to invoke commands which make use of # this user. # chpst (from runit) would be nice to use, but we don't want to # introduce an extra dependency just for this. This may be a # candidate for re-factoring if we switch implementation languages. case $(id -un) in # if monkeysphere user, run the command under bash "$MONKEYSPHERE_USER") bash -c "$@" ;; # if root, su command as monkeysphere user 'root') su "$MONKEYSPHERE_USER" -c "$@" ;; # otherwise, fail *) log error "non-privileged user." ;; esac } # cut out all comments(#) and blank lines from standard input meat() { grep -v -e "^[[:space:]]*#" -e '^$' "$1" } # cut a specified line from standard input cutline() { head --line="$1" "$2" | tail -1 } # make a temporary directory msmktempdir() { mktemp -d ${TMPDIR:-/tmp}/monkeysphere.XXXXXXXXXX } # make a temporary file msmktempfile() { mktemp ${TMPDIR:-/tmp}/monkeysphere.XXXXXXXXXX } # this is a wrapper for doing lock functions. # # it lets us depend on either lockfile-progs (preferred) or procmail's # lockfile, and should lock() { local use_lockfileprogs=true local action="$1" local file="$2" if ! ( type lockfile-create &>/dev/null ) ; then if ! ( type lockfile &>/dev/null ); then failure "Neither lockfile-create nor lockfile are in the path!" fi use_lockfileprogs= fi case "$action" in create) if [ -n "$use_lockfileprogs" ] ; then lockfile-create "$file" || failure "unable to lock '$file'" else lockfile -r 20 "${file}.lock" || failure "unable to lock '$file'" fi log debug "lock created on '$file'." ;; touch) if [ -n "$use_lockfileprogs" ] ; then lockfile-touch --oneshot "$file" else : Nothing to do here fi log debug "lock touched on '$file'." ;; remove) if [ -n "$use_lockfileprogs" ] ; then lockfile-remove "$file" else rm -f "${file}.lock" fi log debug "lock removed on '$file'." ;; *) failure "bad argument for lock subfunction '$action'" esac } # for portability, between gnu date and BSD date. # arguments should be: number longunits format # e.g. advance_date 20 seconds +%F advance_date() { local gnutry local number="$1" local longunits="$2" local format="$3" local shortunits # try things the GNU way first if date -d "$number $longunits" "$format" &>/dev/null; then date -d "$number $longunits" "$format" else # otherwise, convert to (a limited version of) BSD date syntax: case "$longunits" in years) shortunits=y ;; months) shortunits=m ;; weeks) shortunits=w ;; days) shortunits=d ;; hours) shortunits=H ;; minutes) shortunits=M ;; seconds) shortunits=S ;; *) # this is a longshot, and will likely fail; oh well. shortunits="$longunits" esac date "-v+${number}${shortunits}" "$format" fi } # check that characters are in a string (in an AND fashion). # used for checking key capability # check_capability capability a [b...] check_capability() { local usage local capcheck usage="$1" shift 1 for capcheck ; do if echo "$usage" | grep -q -v "$capcheck" ; then return 1 fi done return 0 } # hash of a file file_hash() { if type md5sum &>/dev/null ; then md5sum "$1" elif type md5 &>/dev/null ; then md5 "$1" else failure "Neither md5sum nor md5 are in the path!" fi } # convert escaped characters in pipeline from gpg output back into # original character # FIXME: undo all escape character translation in with-colons gpg # output gpg_unescape() { sed 's/\\x3a/:/g' } # convert nasty chars into gpg-friendly form in pipeline # FIXME: escape everything, not just colons! gpg_escape() { sed 's/:/\\x3a/g' } # prompt for GPG-formatted expiration, and emit result on stdout get_gpg_expiration() { local keyExpire keyExpire="$1" if [ -z "$keyExpire" -a "$PROMPT" = 'true' ]; then cat >&2 < = key expires in n days w = key expires in n weeks m = key expires in n months y = key expires in n years EOF while [ -z "$keyExpire" ] ; do read -p "Key is valid for? (0) " keyExpire if ! test_gpg_expire ${keyExpire:=0} ; then echo "invalid value" >&2 unset keyExpire fi done elif ! test_gpg_expire "$keyExpire" ; then failure "invalid key expiration value '$keyExpire'." fi echo "$keyExpire" } passphrase_prompt() { local prompt="$1" local fifo="$2" local PASS if [ "$DISPLAY" ] && type "${SSH_ASKPASS:-ssh-askpass}" >/dev/null; then printf 'Launching "%s"\n' "${SSH_ASKPASS:-ssh-askpass}" | log info printf '(with prompt "%s")\n' "$prompt" | log debug "${SSH_ASKPASS:-ssh-askpass}" "$prompt" > "$fifo" else read -s -p "$prompt" PASS # Uses the builtin echo, so should not put the passphrase into # the process table. I think. --dkg echo "$PASS" > "$fifo" fi } # remove all lines with specified string from specified file remove_line() { local file local string local tempfile file="$1" string="$2" if [ -z "$file" -o -z "$string" ] ; then return 1 fi if [ ! -e "$file" ] ; then return 1 fi # if the string is in the file... if grep -q -F "$string" "$file" 2>/dev/null ; then tempfile=$(mktemp "${file}.XXXXXXX") || \ failure "Unable to make temp file '${file}.XXXXXXX'" # remove the line with the string, and return 0 grep -v -F "$string" "$file" >"$tempfile" cat "$tempfile" > "$file" rm "$tempfile" return 0 # otherwise return 1 else return 1 fi } # remove all lines with MonkeySphere strings in file remove_monkeysphere_lines() { local file local tempfile file="$1" # return error if file does not exist if [ ! -e "$file" ] ; then return 1 fi # just return ok if the file is empty, since there aren't any # lines to remove if [ ! -s "$file" ] ; then return 0 fi tempfile=$(mktemp "${file}.XXXXXXX") || \ failure "Could not make temporary file '${file}.XXXXXXX'." egrep -v '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$' \ "$file" >"$tempfile" cat "$tempfile" > "$file" rm "$tempfile" } # translate ssh-style path variables %h and %u translate_ssh_variables() { local uname local home uname="$1" path="$2" # get the user's home directory userHome=$(get_homedir "$uname") # translate '%u' to user name path=${path/\%u/"$uname"} # translate '%h' to user home directory path=${path/\%h/"$userHome"} echo "$path" } # test that a string to conforms to GPG's expiration format test_gpg_expire() { echo "$1" | egrep -q "^[0-9]+[mwy]?$" } # check that a file is properly owned, and that all it's parent # directories are not group/other writable check_key_file_permissions() { local uname local path local stat local access local gAccess local oAccess # function to check that the given permission corresponds to writability is_write() { [ "$1" = "w" ] } uname="$1" path="$2" log debug "checking path permission '$path'..." # return 255 if cannot stat file if ! stat=$(ls -ld "$path" 2>/dev/null) ; then log error "could not stat path '$path'." return 255 fi owner=$(echo "$stat" | awk '{ print $3 }') gAccess=$(echo "$stat" | cut -c6) oAccess=$(echo "$stat" | cut -c9) # return 1 if path has invalid owner if [ "$owner" != "$uname" -a "$owner" != 'root' ] ; then log error "improper ownership on path '$path':" log error " $owner != ($uname|root)" return 1 fi # return 2 if path has group or other writability if is_write "$gAccess" || is_write "$oAccess" ; then log error "improper group or other writability on path '$path':" log error " group: $gAccess, other: $oAccess" return 2 fi # return zero if all clear, or go to next path if [ "$path" = '/' ] ; then log debug "path ok." return 0 else check_key_file_permissions "$uname" $(dirname "$path") fi } # return a list of all users on the system list_users() { if type getent &>/dev/null ; then # for linux and FreeBSD systems getent passwd | cut -d: -f1 elif type dscl &>/dev/null ; then # for Darwin systems dscl localhost -list /Search/Users else failure "Neither getent or dscl is in the path! Could not determine list of users." fi } # return the path to the home directory of a user get_homedir() { local uname=${1:-`whoami`} eval "echo ~${uname}" } ### CONVERSION UTILITIES # output the ssh key for a given key ID gpg2ssh() { local keyID keyID="$1" gpg --export "$keyID" | openpgp2ssh "$keyID" 2>/dev/null } # output known_hosts line from ssh key ssh2known_hosts() { local host local port local key # FIXME this does not properly deal with IPv6 hosts using the # standard port (because it's unclear whether their final # colon-delimited address section is a port number or an address # string) host=${1%:*} port=${1##*:} key="$2" # specify the host and port properly for new ssh known_hosts # format if [ "$port" != "$host" ] ; then host="[${host}]:${port}" fi printf "%s %s MonkeySphere%s\n" "$host" "$key" "$DATE" } # output authorized_keys line from ssh key ssh2authorized_keys() { local userID local key userID="$1" key="$2" printf "%s MonkeySphere%s %s\n" "$key" "$DATE" "$userID" } # convert key from gpg to ssh known_hosts format gpg2known_hosts() { local host local keyID local key host="$1" keyID="$2" key=$(gpg2ssh "$keyID") # NOTE: it seems that ssh-keygen -R removes all comment fields from # all lines in the known_hosts file. why? # NOTE: just in case, the COMMENT can be matched with the # following regexp: # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$' printf "%s %s MonkeySphere%s\n" "$host" "$key" "$DATE" } # convert key from gpg to ssh authorized_keys format gpg2authorized_keys() { local userID local keyID local key userID="$1" keyID="$2" key=$(gpg2ssh "$keyID") # NOTE: just in case, the COMMENT can be matched with the # following regexp: # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$' printf "%s MonkeySphere%s %s\n" "$key" "$DATE" "$userID" } ### GPG UTILITIES # retrieve all keys with given user id from keyserver # FIXME: need to figure out how to retrieve all matching keys # (not just first N (5 in this case)) gpg_fetch_userid() { local returnCode=0 local userID if [ "$CHECK_KEYSERVER" != 'true' ] ; then return 0 fi userID="$1" log verbose " checking keyserver $KEYSERVER... " echo 1,2,3,4,5 | \ gpg --quiet --batch --with-colons \ --command-fd 0 --keyserver "$KEYSERVER" \ --search ="$userID" &>/dev/null returnCode="$?" return "$returnCode" } ######################################################################## ### PROCESSING FUNCTIONS # userid and key policy checking # the following checks policy on the returned keys # - checks that full key has appropriate valididy (u|f) # - checks key has specified capability (REQUIRED_*_KEY_CAPABILITY) # - checks that requested user ID has appropriate validity # (see /usr/share/doc/gnupg/DETAILS.gz) # output is one line for every found key, in the following format: # # flag:sshKey # # "flag" is an acceptability flag, 0 = ok, 1 = bad # "sshKey" is the translated gpg key # # all log output must go to stderr, as stdout is used to pass the # flag:sshKey to the calling function. # # expects global variable: "MODE" process_user_id() { local returnCode=0 local userID local requiredCapability local requiredPubCapability local gpgOut local type local validity local keyid local uidfpr local usage local keyOK local uidOK local lastKey local lastKeyOK local fingerprint userID="$1" # set the required key capability based on the mode if [ "$MODE" = 'known_hosts' ] ; then requiredCapability="$REQUIRED_HOST_KEY_CAPABILITY" elif [ "$MODE" = 'authorized_keys' ] ; then requiredCapability="$REQUIRED_USER_KEY_CAPABILITY" fi requiredPubCapability=$(echo "$requiredCapability" | tr "[:lower:]" "[:upper:]") # fetch the user ID if necessary/requested gpg_fetch_userid "$userID" # output gpg info for (exact) userid and store gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \ --with-fingerprint --with-fingerprint \ ="$userID" 2>/dev/null) || returnCode="$?" # if the gpg query return code is not 0, return 1 if [ "$returnCode" -ne 0 ] ; then log verbose " no primary keys found." return 1 fi # loop over all lines in the gpg output and process. echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \ while IFS=: read -r type validity keyid uidfpr usage ; do # process based on record type case $type in 'pub') # primary keys # new key, wipe the slate keyOK= uidOK= lastKey=pub lastKeyOK= fingerprint= log verbose " primary key found: $keyid" # if overall key is not valid, skip if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then log debug " - unacceptable primary key validity ($validity)." continue fi # if overall key is disabled, skip if check_capability "$usage" 'D' ; then log debug " - key disabled." continue fi # if overall key capability is not ok, skip if ! check_capability "$usage" $requiredPubCapability ; then log debug " - unacceptable primary key capability ($usage)." continue fi # mark overall key as ok keyOK=true # mark primary key as ok if capability is ok if check_capability "$usage" $requiredCapability ; then lastKeyOK=true fi ;; 'uid') # user ids if [ "$lastKey" != pub ] ; then log verbose " ! got a user ID after a sub key?! user IDs should only follow primary keys!" continue fi # if an acceptable user ID was already found, skip if [ "$uidOK" = 'true' ] ; then continue fi # if the user ID does matches... if [ "$(echo "$uidfpr" | gpg_unescape)" = "$userID" ] ; then # and the user ID validity is ok if [ "$validity" = 'u' -o "$validity" = 'f' ] ; then # mark user ID acceptable uidOK=true else log debug " - unacceptable user ID validity ($validity)." fi else continue fi # output a line for the primary key # 0 = ok, 1 = bad if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then log verbose " * acceptable primary key." if [ -z "$sshKey" ] ; then log error " ! primary key could not be translated (not RSA?)." else echo "0:${sshKey}" fi else log debug " - unacceptable primary key." if [ -z "$sshKey" ] ; then log debug " ! primary key could not be translated (not RSA?)." else echo "1:${sshKey}" fi fi ;; 'sub') # sub keys # unset acceptability of last key lastKey=sub lastKeyOK= fingerprint= # don't bother with sub keys if the primary key is not valid if [ "$keyOK" != true ] ; then continue fi # don't bother with sub keys if no user ID is acceptable: if [ "$uidOK" != true ] ; then continue fi # if sub key validity is not ok, skip if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then log debug " - unacceptable sub key validity ($validity)." continue fi # if sub key capability is not ok, skip if ! check_capability "$usage" $requiredCapability ; then log debug " - unacceptable sub key capability ($usage)." continue fi # mark sub key as ok lastKeyOK=true ;; 'fpr') # key fingerprint fingerprint="$uidfpr" sshKey=$(gpg2ssh "$fingerprint") # if the last key was the pub key, skip if [ "$lastKey" = pub ] ; then continue fi # output a line for the sub key # 0 = ok, 1 = bad if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then log verbose " * acceptable sub key." if [ -z "$sshKey" ] ; then log error " ! sub key could not be translated (not RSA?)." else echo "0:${sshKey}" fi else log debug " - unacceptable sub key." if [ -z "$sshKey" ] ; then log debug " ! sub key could not be translated (not RSA?)." else echo "1:${sshKey}" fi fi ;; esac done | sort -t: -k1 -n -r # NOTE: this last sort is important so that the "good" keys (key # flag '0') come last. This is so that they take precedence when # being processed in the key files over "bad" keys (key flag '1') } # process a single host in the known_host file process_host_known_hosts() { local host local userID local noKey= local nKeys local nKeysOK local ok local sshKey local tmpfile # set the key processing mode export MODE='known_hosts' host="$1" userID="ssh://${host}" log verbose "processing: $host" nKeys=0 nKeysOK=0 IFS=$'\n' for line in $(process_user_id "${userID}") ; do # note that key was found nKeys=$((nKeys+1)) ok=$(echo "$line" | cut -d: -f1) sshKey=$(echo "$line" | cut -d: -f2) if [ -z "$sshKey" ] ; then continue fi # remove any old host key line, and note if removed nothing is # removed remove_line "$KNOWN_HOSTS" "$sshKey" || noKey=true # if key OK, add new host line if [ "$ok" -eq '0' ] ; then # note that key was found ok nKeysOK=$((nKeysOK+1)) # hash if specified if [ "$HASH_KNOWN_HOSTS" = 'true' ] ; then # FIXME: this is really hackish cause ssh-keygen won't # hash from stdin to stdout tmpfile=$(mktemp ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX) ssh2known_hosts "$host" "$sshKey" > "$tmpfile" ssh-keygen -H -f "$tmpfile" 2>/dev/null cat "$tmpfile" >> "$KNOWN_HOSTS" rm -f "$tmpfile" "${tmpfile}.old" else ssh2known_hosts "$host" "$sshKey" >> "$KNOWN_HOSTS" fi # log if this is a new key to the known_hosts file if [ "$noKey" ] ; then log info "* new key for $host added to known_hosts file." fi fi done # if at least one key was found... if [ "$nKeys" -gt 0 ] ; then # if ok keys were found, return 0 if [ "$nKeysOK" -gt 0 ] ; then return 0 # else return 2 else return 2 fi # if no keys were found, return 1 else return 1 fi } # update the known_hosts file for a set of hosts listed on command # line update_known_hosts() { local returnCode=0 local nHosts local nHostsOK local nHostsBAD local fileCheck local host # the number of hosts specified on command line nHosts="$#" nHostsOK=0 nHostsBAD=0 # touch the known_hosts file so that the file permission check # below won't fail upon not finding the file (umask 0022 && touch "$KNOWN_HOSTS") # check permissions on the known_hosts file path check_key_file_permissions $(whoami) "$KNOWN_HOSTS" || failure # create a lockfile on known_hosts: lock create "$KNOWN_HOSTS" # FIXME: we're discarding any pre-existing EXIT trap; is this bad? trap "lock remove $KNOWN_HOSTS" EXIT # note pre update file checksum fileCheck="$(file_hash "$KNOWN_HOSTS")" for host ; do # process the host process_host_known_hosts "$host" || returnCode="$?" # note the result case "$returnCode" in 0) nHostsOK=$((nHostsOK+1)) ;; 2) nHostsBAD=$((nHostsBAD+1)) ;; esac # touch the lockfile, for good measure. lock touch "$KNOWN_HOSTS" done # remove the lockfile and the trap lock remove "$KNOWN_HOSTS" trap - EXIT # note if the known_hosts file was updated if [ "$(file_hash "$KNOWN_HOSTS")" != "$fileCheck" ] ; then log debug "known_hosts file updated." fi # if an acceptable host was found, return 0 if [ "$nHostsOK" -gt 0 ] ; then return 0 # else if no ok hosts were found... else # if no bad host were found then no hosts were found at all, # and return 1 if [ "$nHostsBAD" -eq 0 ] ; then return 1 # else if at least one bad host was found, return 2 else return 2 fi fi } # process hosts from a known_hosts file process_known_hosts() { local hosts # exit if the known_hosts file does not exist if [ ! -e "$KNOWN_HOSTS" ] ; then failure "known_hosts file '$KNOWN_HOSTS' does not exist." fi log debug "processing known_hosts file:" log debug " $KNOWN_HOSTS" hosts=$(meat "$KNOWN_HOSTS" | cut -d ' ' -f 1 | grep -v '^|.*$' | tr , ' ' | tr '\n' ' ') if [ -z "$hosts" ] ; then log debug "no hosts to process." return fi # take all the hosts from the known_hosts file (first # field), grep out all the hashed hosts (lines starting # with '|')... update_known_hosts $hosts } # process uids for the authorized_keys file process_uid_authorized_keys() { local userID local nKeys local nKeysOK local ok local sshKey # set the key processing mode export MODE='authorized_keys' userID="$1" log verbose "processing: $userID" nKeys=0 nKeysOK=0 IFS=$'\n' for line in $(process_user_id "$userID") ; do # note that key was found nKeys=$((nKeys+1)) ok=$(echo "$line" | cut -d: -f1) sshKey=$(echo "$line" | cut -d: -f2) if [ -z "$sshKey" ] ; then continue fi # remove the old host key line remove_line "$AUTHORIZED_KEYS" "$sshKey" # if key OK, add new host line if [ "$ok" -eq '0' ] ; then # note that key was found ok nKeysOK=$((nKeysOK+1)) ssh2authorized_keys "$userID" "$sshKey" >> "$AUTHORIZED_KEYS" fi done # if at least one key was found... if [ "$nKeys" -gt 0 ] ; then # if ok keys were found, return 0 if [ "$nKeysOK" -gt 0 ] ; then return 0 # else return 2 else return 2 fi # if no keys were found, return 1 else return 1 fi } # update the authorized_keys files from a list of user IDs on command # line update_authorized_keys() { local returnCode=0 local userID local nIDs local nIDsOK local nIDsBAD local fileCheck # the number of ids specified on command line nIDs="$#" nIDsOK=0 nIDsBAD=0 log debug "updating authorized_keys file:" log debug " $AUTHORIZED_KEYS" # check permissions on the authorized_keys file path check_key_file_permissions $(whoami) "$AUTHORIZED_KEYS" || failure # create a lockfile on authorized_keys lock create "$AUTHORIZED_KEYS" # FIXME: we're discarding any pre-existing EXIT trap; is this bad? trap "lock remove $AUTHORIZED_KEYS" EXIT # note pre update file checksum fileCheck="$(file_hash "$AUTHORIZED_KEYS")" # remove any monkeysphere lines from authorized_keys file remove_monkeysphere_lines "$AUTHORIZED_KEYS" for userID ; do # process the user ID, change return code if key not found for # user ID process_uid_authorized_keys "$userID" || returnCode="$?" # note the result case "$returnCode" in 0) nIDsOK=$((nIDsOK+1)) ;; 2) nIDsBAD=$((nIDsBAD+1)) ;; esac # touch the lockfile, for good measure. lock touch "$AUTHORIZED_KEYS" done # remove the lockfile and the trap lock remove "$AUTHORIZED_KEYS" # remove the trap trap - EXIT # note if the authorized_keys file was updated if [ "$(file_hash "$AUTHORIZED_KEYS")" != "$fileCheck" ] ; then log debug "authorized_keys file updated." fi # if an acceptable id was found, return 0 if [ "$nIDsOK" -gt 0 ] ; then return 0 # else if no ok ids were found... else # if no bad ids were found then no ids were found at all, and # return 1 if [ "$nIDsBAD" -eq 0 ] ; then return 1 # else if at least one bad id was found, return 2 else return 2 fi fi } # process an authorized_user_ids file for authorized_keys process_authorized_user_ids() { local line local nline local userIDs authorizedUserIDs="$1" # exit if the authorized_user_ids file is empty if [ ! -e "$authorizedUserIDs" ] ; then failure "authorized_user_ids file '$authorizedUserIDs' does not exist." fi log debug "processing authorized_user_ids file:" log debug " $authorizedUserIDs" # check permissions on the authorized_user_ids file path check_key_file_permissions $(whoami) "$authorizedUserIDs" || failure if ! meat "$authorizedUserIDs" >/dev/null ; then log debug " no user IDs to process." return fi nline=0 # extract user IDs from authorized_user_ids file IFS=$'\n' for line in $(meat "$authorizedUserIDs") ; do userIDs["$nline"]="$line" nline=$((nline+1)) done update_authorized_keys "${userIDs[@]}" } # takes a gpg key or keys on stdin, and outputs a list of # fingerprints, one per line: list_primary_fingerprints() { local fake=$(msmktempdir) GNUPGHOME="$fake" gpg --no-tty --quiet --import GNUPGHOME="$fake" gpg --with-colons --fingerprint --list-keys | \ awk -F: '/^fpr:/{ print $10 }' rm -rf "$fake" } check_cruft_file() { local loc="$1" local version="$2" if [ -e "$loc" ] ; then printf "! The file '%s' is no longer used by\n monkeysphere (as of version %s), and can be removed.\n\n" "$loc" "$version" | log info fi } check_upgrade_dir() { local loc="$1" local version="$2" if [ -d "$loc" ] ; then printf "The presence of directory '%s' indicates that you have\nnot yet completed a monkeysphere upgrade.\nYou should probably run the following script:\n %s/transitions/%s\n\n" "$loc" "$SYSSHAREDIR" "$version" | log info fi } ## look for cruft from old versions of the monkeysphere, and notice if ## upgrades have not been run: report_cruft() { check_upgrade_dir "${SYSCONFIGDIR}/gnupg-host" 0.23 check_upgrade_dir "${SYSCONFIGDIR}/gnupg-authentication" 0.23 check_cruft_file "${SYSCONFIGDIR}/gnupg-authentication.conf" 0.23 check_cruft_file "${SYSCONFIGDIR}/gnupg-host.conf" 0.23 local found= for foo in "${SYSDATADIR}/backup-from-"*"-transition" ; do if [ -d "$foo" ] ; then printf "! %s\n" "$foo" | log info found=true fi done if [ "$found" ] ; then printf "The directories above are backups left over from a monkeysphere transition.\nThey may contain copies of sensitive data (host keys, certifier lists), but\nthey are no longer needed by monkeysphere.\nYou may remove them at any time.\n\n" | log info fi } ="hl kwa">msgstr "您是否確定要消除有記號的條目?"
  • msgid "Assemblies"
  • msgstr "製成品"
  • msgid "Assemblies restocked!"
  • msgstr "製成品已重新進貨"
  • msgid "Assembly"
  • msgstr "製成品"
  • msgid "Assembly stocked!"
  • msgstr "製成品已進貨"
  • msgid "Asset"
  • msgstr "資產"
  • msgid "Attachment"
  • msgstr "附件"
  • msgid "Audit Control"
  • msgstr "審核控制"
  • msgid "Audit trail removed up to"
  • msgstr "移除到此為止的審核線索"
  • msgid "Audit trails disabled"
  • msgstr "審核線索已開動"
  • msgid "Audit trails enabled"
  • msgstr "審核線索已關閉"
  • msgid "Aug"
  • msgstr "八月"
  • msgid "August"
  • msgstr "八月"
  • msgid "Average Cost"
  • msgstr "平均成本"
  • msgid "Avg Cost"
  • msgstr "平均成本"
  • msgid "BIC"
  • msgstr "銀行識別編號(BIC)"
  • msgid "BOM"
  • msgstr "材料清單"
  • msgid "Backup"
  • msgstr "備份"
  • msgid "Backup sent to"
  • msgstr "備份寄送到"
  • msgid "Balance"
  • msgstr "餘額"
  • msgid "Balance Sheet"
  • msgstr "資產負債表"
  • msgid "Based on"
  • msgstr "基於"
  • msgid "Bcc"
  • msgstr "不顯示抄送"
  • msgid "Before Deduction"
  • msgstr "扣除之前"
  • msgid "Beginning Balance"
  • msgstr "起始餘額"
  • msgid "Below"
  • msgstr "以下"
  • msgid "Billing Address"
  • msgstr "帳單地址"
  • msgid "Bin"
  • msgstr "箱"
  • msgid "Bin List"
  • msgstr "箱的明細表"
  • msgid "Bin Lists"
  • msgstr "箱的明細表"
  • msgid "Books are open"
  • msgstr "帳簿已開啟"
  • msgid "Break"
  • msgstr "休息"
  • msgid "Business"
  • msgstr "業務"
  • msgid "Business Number"
  • msgstr "業務編號"
  • msgid "Business deleted!"
  • msgstr "已刪除業務!"
  • msgid "Business saved!"
  • msgstr "已儲存業務!"
  • msgid "C"
  • msgstr ""
  • msgid "COGS"
  • msgstr "貨銷成本"
  • msgid "COGS account does not exist!"
  • msgstr "貨銷成本帳戶不存在!"
  • msgid "Cannot add timecard for a completed job!"
  • msgstr "工作已完成,不能再加工時卡!"
  • msgid "Cannot change timecard for a completed job!"
  • msgstr "工作已完成,不能再改工時卡!"
  • msgid "Cannot create Assembly"
  • msgstr "不能建立製成品"
  • msgid "Cannot create Labor"
  • msgstr "不能建立直接人工"
  • msgid "Cannot create Lock!"
  • msgstr "不能建立鎖!"
  • msgid "Cannot create Part"
  • msgstr "不能建立零件"
  • msgid "Cannot create Service"
  • msgstr "不能建立服務"
  • msgid "Cannot delete Timecard!"
  • msgstr "不能建立工時卡"
  • msgid "Cannot delete account!"
  • msgstr "不能刪除帳戶!"
  • msgid "Cannot delete customer!"
  • msgstr "不能刪除客戶!"
  • msgid "Cannot delete default account!"
  • msgstr "不能刪除預設帳戶!"
  • msgid "Cannot delete invoice!"
  • msgstr "不能刪除發票!"
  • msgid "Cannot delete item!"
  • msgstr "不能刪除項目!"
  • msgid "Cannot delete order!"
  • msgstr "不能刪除定單!"
  • msgid "Cannot delete quotation!"
  • msgstr "不能刪除報價單!"
  • msgid "Cannot delete transaction!"
  • msgstr "不能刪除交易!"
  • msgid "Cannot delete vendor!"
  • msgstr "不能刪除供應商!"
  • msgid "Cannot post Payment!"
  • msgstr "不能加入款項!"
  • msgid "Cannot post Receipt!"
  • msgstr "不能加入收據!"
  • msgid "Cannot post invoice for a closed period!"
  • msgstr "不能在已關閉的時段內加入發票!"
  • msgid "Cannot post invoice!"
  • msgstr "不能加入發票!"
  • msgid "Cannot post payment for a closed period!"
  • msgstr "不能在已關閉的時段內加入款項!"
  • msgid "Cannot post transaction for a closed period!"
  • msgstr "不能在已關閉的時段內加入交易!"
  • msgid "Cannot post transaction with a debit and credit entry for the same account!"
  • msgstr "在交易同一帳戶不能又出現在借方又出現在貸方!"
  • msgid "Cannot post transaction!"
  • msgstr "不能加入交易!"
  • msgid "Cannot remove files!"
  • msgstr "不能移除檔案!"
  • msgid "Cannot save account!"
  • msgstr "不能儲存帳戶!"
  • msgid "Cannot save defaults!"
  • msgstr "不能儲存預設!"
  • msgid "Cannot save order!"
  • msgstr "不能儲存定單!"
  • msgid "Cannot save preferences!"
  • msgstr "不能儲存優先選擇!"
  • msgid "Cannot save quotation!"
  • msgstr "不能儲存報價單!"
  • msgid "Cannot save timecard for a closed period!"
  • msgstr "不能在已關閉的時段內儲存工時卡!"
  • msgid "Cannot save timecard!"
  • msgstr "不能儲存工時卡!"
  • msgid "Cannot set account for more than one of AR, AP or IC"
  • msgstr "不能設置多於一個應收帳款, 應付帳款或IC"
  • msgid "Cannot set multiple options for"
  • msgstr "不能對以下物件設定多個選項:"
  • msgid "Cannot set multiple options for Item"
  • msgstr "不能對以物件設定多個選項"
  • msgid "Cannot stock Assembly!"
  • msgstr "不能把製成品入貨!"
  • msgid "Cannot stock assemblies!"
  • msgstr "不能把製成品入貨!"
  • msgid "Cash"
  • msgstr "現金"
  • msgid "Cc"
  • msgstr "抄送"
  • msgid "Change"
  • msgstr "更改"
  • msgid "Change Admin Password"
  • msgstr "更改管理員密碼"
  • msgid "Change Password"
  • msgstr "更改密碼"
  • msgid "Charge"
  • msgstr "收費"
  • msgid "Chart of Accounts"
  • msgstr "會計科目表"
  • msgid "Check"
  • msgstr "檢查"
  • msgid "Check Inventory"
  • msgstr "檢查存貨清單"
  • msgid "Checks"
  • msgstr "檢查"
  • msgid "City"
  • msgstr "城市"
  • msgid "Cleared"
  • msgstr "已清除"
  • msgid "Click on login name to edit!"
  • msgstr "請按登入名稱以進行修改!"
  • msgid "Clocked"
  • msgstr "計算出的時間"
  • msgid "Close Books up to"
  • msgstr "關閉到此為止的帳簿:"
  • msgid "Closed"
  • msgstr "已關閉"
  • msgid "Code"
  • msgstr "編碼"
  • msgid "Code missing!"
  • msgstr "未指明編碼!"
  • msgid "Company"
  • msgstr "公司"
  • msgid "Company Name"
  • msgstr "公司名稱"
  • msgid "Compare to"
  • msgstr "對照"
  • msgid "Completed"
  • msgstr "完成了"
  • msgid "Components"
  • msgstr "零件"
  • msgid "Confirm"
  • msgstr "確認"
  • msgid "Confirm!"
  • msgstr "入帳成功!"
  • msgid "Connect to"
  • msgstr "連結到"
  • msgid "Contact"
  • msgstr "連絡人"
  • msgid "Continue"
  • msgstr "繼續"
  • msgid "Contra"
  • msgstr "相反"
  • msgid "Copies"
  • msgstr "副本"
  • msgid "Copy to COA"
  • msgstr "複製到 COA"
  • msgid "Cost"
  • msgstr "成本"
  • msgid "Cost Center"
  • msgstr "成本中心"
  • msgid "Could not save pricelist!"
  • msgstr "不能儲存價格清單!"
  • msgid "Could not save!"
  • msgstr "不能儲存!"
  • msgid "Could not transfer Inventory!"
  • msgstr "存貨清單不能轉移!"
  • msgid "Country"
  • msgstr "國家"
  • msgid "Create Chart of Accounts"
  • msgstr "建立帳戶圖表"
  • msgid "Create Dataset"
  • msgstr "建立資料集"
  • msgid "Credit"
  • msgstr "貸方"
  • msgid "Credit Limit"
  • msgstr "信用額度"
  • msgid "Curr"
  • msgstr "目前"
  • msgid "Currency"
  • msgstr "幣別"
  • msgid "Current"
  • msgstr "現有"
  • msgid "Current Earnings"
  • msgstr "現有收益"
  • msgid "Customer"
  • msgstr "客戶"
  • msgid "Customer History"
  • msgstr "客戶歷史"
  • msgid "Customer Number"
  • msgstr "客戶編號"
  • msgid "Customer deleted!"
  • msgstr "已刪除客戶!"
  • msgid "Customer missing!"
  • msgstr "未指明客戶!"
  • msgid "Customer not on file!"
  • msgstr "沒有此客戶的記錄!"
  • msgid "Customer saved!"
  • msgstr "已儲存客戶!"
  • msgid "Customers"
  • msgstr "客戶"
  • msgid "DBI not installed!"
  • msgstr "未安裝 DBI 模組!"
  • msgid "DOB"
  • msgstr "出生日期"
  • msgid "Database"
  • msgstr "資料庫"
  • msgid "Database Administration"
  • msgstr "資料庫管理"
  • msgid "Database Driver not checked!"
  • msgstr "未選定資料庫驅動程式!"
  • msgid "Database Host"
  • msgstr "資料庫主機"
  • msgid "Database User missing!"
  • msgstr "未指明資料庫使用者!"
  • msgid "Dataset"
  • msgstr "資料集"
  • msgid "Dataset is newer than version!"
  • msgstr "較新資料集!"
  • msgid "Dataset missing!"
  • msgstr "未指明資料集!"
  • msgid "Dataset updated!"
  • msgstr "已更新資料集!"
  • msgid "Date"
  • msgstr "日期"
  • msgid "Date Format"
  • msgstr "日期格式"
  • msgid "Date Paid"
  • msgstr "付款日期"
  • msgid "Date Received"
  • msgstr "收款日期"
  • msgid "Date missing!"
  • msgstr "未指明日期!"
  • msgid "Date received missing!"
  • msgstr "未指明收款日期!"
  • msgid "Date worked"
  • msgstr "工作日期"
  • msgid "Day"
  • msgstr "日"
  • msgid "Day(s)"
  • msgstr "日"
  • msgid "Days"
  • msgstr "日"
  • msgid "Debit"
  • msgstr "借方"
  • msgid "Dec"
  • msgstr "十二月"
  • msgid "December"
  • msgstr "十二月"
  • msgid "Decimalplaces"
  • msgstr "小數的位置"
  • msgid "Decrease"
  • msgstr "減少"
  • msgid "Deduct after"
  • msgstr "減少以後"
  • msgid "Deduction deleted!"
  • msgstr "已刪除減少!"
  • msgid "Deduction saved!"
  • msgstr "已儲存減少!"
  • msgid "Deductions"
  • msgstr "減除額"
  • msgid "Default Template"
  • msgstr "預設模版"
  • msgid "Defaults"
  • msgstr "預設"
  • msgid "Defaults saved!"
  • msgstr "已儲存預設!"
  • msgid "Delete"
  • msgstr "刪除"
  • msgid "Delete Account"
  • msgstr "刪除帳戶"
  • msgid "Delete Dataset"
  • msgstr "刪除資料集"
  • msgid "Delete Schedule"
  • msgstr "刪除時間表"
  • msgid "Deleting a language will also delete the templates for the language"
  • msgstr "刪除語言將會刪除它的模版"
  • msgid "Delivery Date"
  • msgstr "到期日"
  • msgid "Department"
  • msgstr "部門"
  • msgid "Department deleted!"
  • msgstr "已刪除部門!"
  • msgid "Department saved!"
  • msgstr "已儲存部門!"
  • msgid "Departments"
  • msgstr "部門"
  • msgid "Deposit"
  • msgstr "存款"
  • msgid "Description"
  • msgstr "說明"
  • msgid "Description Translations"
  • msgstr "翻譯描述"
  • msgid "Description missing!"
  • msgstr "未指明描述!"
  • msgid "Detail"
  • msgstr "詳情"
  • msgid "Difference"
  • msgstr "差異"
  • msgid "Directory"
  • msgstr "目錄"
  • msgid "Discount"
  • msgstr "折扣"
  • msgid "Done"
  • msgstr "已完成"
  • msgid "Drawing"
  • msgstr "圖畫"
  • msgid "Driver"
  • msgstr "驅動程式"
  • msgid "Dropdown Limit"
  • msgstr "限制"
  • msgid "Due Date"
  • msgstr "到期日"
  • msgid "Due Date missing!"
  • msgstr "未指明到期日!"
  • msgid "E-mail"
  • msgstr "電子郵件"
  • msgid "E-mail Statement to"
  • msgstr "電郵會計賬到"
  • msgid "E-mail address missing!"
  • msgstr "未指明電子郵件位址!"
  • msgid "E-mail message"
  • msgstr "電子郵件訊息"
  • msgid "E-mailed"
  • msgstr "已電郵"
  • msgid "Edit"
  • msgstr "編輯"
  • msgid "Edit AP Transaction"
  • msgstr "編輯應付交易"
  • msgid "Edit AR Transaction"
  • msgstr "編輯應收交易"
  • msgid "Edit Account"
  • msgstr "編輯帳戶"
  • msgid "Edit Assembly"
  • msgstr "編輯製成品"
  • msgid "Edit Business"
  • msgstr "編輯業務"
  • msgid "Edit Cash Transfer Transaction"
  • msgstr "編輯現金轉移"
  • msgid "Edit Customer"
  • msgstr "編輯客戶"
  • msgid "Edit Deduction"
  • msgstr "編輯減除額"
  • msgid "Edit Department"
  • msgstr "編輯部門"
  • msgid "Edit Description Translations"
  • msgstr "編輯翻譯描述"
  • msgid "Edit Employee"
  • msgstr "編輯職員"
  • msgid "Edit GIFI"
  • msgstr "編輯GIFI"
  • msgid "Edit General Ledger Transaction"
  • msgstr "編輯總帳"
  • msgid "Edit Group"
  • msgstr "編輯組別"
  • msgid "Edit Job"
  • msgstr "編輯工作"
  • msgid "Edit Labor/Overhead"
  • msgstr "編輯直接人工/經常費用"
  • msgid "Edit Language"
  • msgstr "編輯語言"
  • msgid "Edit POS Invoice"
  • msgstr "編輯POS"
  • msgid "Edit Part"
  • msgstr "編輯零件"
  • msgid "Edit Preferences for"
  • msgstr "設定使用者"
  • msgid "Edit Pricegroup"
  • msgstr "編輯價格組別"
  • msgid "Edit Project"
  • msgstr "編輯項目"
  • msgid "Edit Purchase Order"
  • msgstr "編輯採購單"
  • msgid "Edit Quotation"
  • msgstr "編輯報價單"
  • msgid "Edit Request for Quotation"
  • msgstr "編輯報價單要求"
  • msgid "Edit SIC"
  • msgstr "編輯標準工業分類代碼"
  • msgid "Edit Sales Invoice"
  • msgstr "編輯銷售發票"
  • msgid "Edit Sales Order"
  • msgstr "編輯銷貨單"
  • msgid "Edit Service"
  • msgstr "編輯服務"
  • msgid "Edit Template"
  • msgstr "編輯模版"
  • msgid "Edit Timecard"
  • msgstr "編輯工時卡"
  • msgid "Edit User"
  • msgstr "編輯使用者"
  • msgid "Edit Vendor"
  • msgstr "編輯供應商"
  • msgid "Edit Vendor Invoice"
  • msgstr "編輯供應商發票"
  • msgid "Edit Warehouse"
  • msgstr "編輯倉庫"
  • msgid "Employee"
  • msgstr "職員"
  • msgid "Employee Name"
  • msgstr "職員姓名"
  • msgid "Employee Number"
  • msgstr "職員編號"
  • msgid "Employee deleted!"
  • msgstr "已刪除職員!"
  • msgid "Employee pays"
  • msgstr "付款給職員"
  • msgid "Employee saved!"
  • msgstr "已儲存職員!"
  • msgid "Employees"
  • msgstr "職員"
  • msgid "Employer"
  • msgstr "雇主"
  • msgid "Employer pays"
  • msgstr "付款給雇主"
  • msgid "Enddate"
  • msgstr "結束日"
  • msgid "Ends"
  • msgstr "完結"
  • msgid "Enforce transaction reversal for all dates"
  • msgstr "強制所有日期的交易回復"
  • msgid "Enter up to 3 letters separated by a colon (i.e CAD:USD:EUR) for your native and foreign currencies"
  • msgstr "請鍵入以冒號分隔的英文字母, 每項不超過三個字 (如 CAD:USD:EUR), 作為您的本國及外國貨幣"
  • msgid "Equity"
  • msgstr "股權"
  • msgid "Every"
  • msgstr "每"
  • msgid "Excempt age <"
  • msgstr "不包括年齡 <"
  • msgid "Exch"
  • msgstr "匯率"
  • msgid "Exchange Rate"
  • msgstr "匯率"
  • msgid "Exchange rate for payment missing!"
  • msgstr "未指明付款的匯率!"
  • msgid "Exchange rate missing!"
  • msgstr "未指明匯率!"
  • msgid "Existing Datasets"
  • msgstr "既有的資料集"
  • msgid "Expense"
  • msgstr "費用"
  • msgid "Expense account does not exist!"
  • msgstr "費用帳戶不存在!"
  • msgid "Expense/Asset"
  • msgstr "費用/資產"
  • msgid "Extended"
  • msgstr "總價"
  • msgid "FX"
  • msgstr "外幣兌換"
  • msgid "Failed to save order!"
  • msgstr "儲存訂單時出錯!"
  • msgid "Fax"
  • msgstr "傳真"
  • msgid "Feb"
  • msgstr "二月"
  • msgid "February"
  • msgstr "二月"
  • msgid "For"
  • msgstr "重覆"
  • msgid "Foreign Exchange Gain"
  • msgstr "外匯收益"
  • msgid "Foreign Exchange Loss"
  • msgstr "外匯損失"
  • msgid "Friday"
  • msgstr "星期五"
  • msgid "From"
  • msgstr "從"
  • msgid "From Warehouse"
  • msgstr "從貨倉"
  • msgid "GIFI"
  • msgstr "財務訊息通用索引(GIFI)"
  • msgid "GIFI deleted!"
  • msgstr "GIFI已刪除!"
  • msgid "GIFI missing!"
  • msgstr "未指明 GIFI!"
  • msgid "GIFI saved!"
  • msgstr "已儲存!"
  • msgid "GL"
  • msgstr "總帳"
  • msgid "GL Reference Number"
  • msgstr "總帳索引編號"
  • msgid "GL Transaction"
  • msgstr "總帳交易"
  • msgid "General Ledger"
  • msgstr "總帳"
  • msgid "Generate"
  • msgstr "生成"
  • msgid "Generate Orders"
  • msgstr "生成訂單"
  • msgid "Generate Purchase Orders"
  • msgstr "生成採購單"
  • msgid "Generate Purchase Orders from Sales Order"
  • msgstr "由銷貨單生成採購單"
  • msgid "Generate Sales Order"
  • msgstr "生成銷貨單"
  • msgid "Generate Sales Order from Purchase Orders"
  • msgstr "由採購單生成銷貨單"
  • msgid "Generate Sales Orders"
  • msgstr "生成銷貨單"
  • msgid "Goods & Services"
  • msgstr "貨物及服務"
  • msgid "Group"
  • msgstr "組別"
  • msgid "Group Items"
  • msgstr "組別的項目"
  • msgid "Group Translations"
  • msgstr "組別的翻譯"
  • msgid "Group deleted!"
  • msgstr "組別已被刪除!"
  • msgid "Group missing!"
  • msgstr "未指明組別!"
  • msgid "Group saved!"
  • msgstr "組別已被儲存!"
  • msgid "Groups"
  • msgstr "所有組別"
  • msgid "HR"
  • msgstr "人事管理"
  • msgid "HTML Templates"
  • msgstr "HTML 模版"
  • msgid "Heading"
  • msgstr "標題"
  • msgid "History"
  • msgstr "歷史"
  • msgid "Home Phone"
  • msgstr "住宅電話"
  • msgid "Host"
  • msgstr "主機"
  • msgid "Hostname missing!"
  • msgstr "未指明主機名稱!"
  • msgid "IBAN"
  • msgstr "國際銀行帳號(IBAN)"
  • msgid "ID"
  • msgstr "識別編號"
  • msgid "Image"
  • msgstr "影像"
  • msgid "In-line"
  • msgstr "行內"
  • msgid "Inactive"
  • msgstr "非活躍的"
  • msgid "Include Exchange Rate Difference"
  • msgstr "包含外匯差距"
  • msgid "Include Payment"
  • msgstr "包括付款"
  • msgid "Include in Report"
  • msgstr "一併顯示"
  • msgid "Include in drop-down menus"
  • msgstr "包含在下拉式選單中"
  • msgid "Income"
  • msgstr "收入"
  • msgid "Income Statement"
  • msgstr "損益表"
  • msgid "Income account does not exist!"
  • msgstr "收入帳戶不存在!"
  • msgid "Incorrect Dataset version!"
  • msgstr "資料集版本錯誤!"
  • msgid "Increase"
  • msgstr "增加"
  • msgid "Individual Items"
  • msgstr "個別的項目"
  • msgid "Internal Notes"
  • msgstr "內部備忘錄"
  • msgid "Inventory"
  • msgstr "庫存"
  • msgid "Inventory account does not exist!"
  • msgstr "庫存數帳戶不存在!"
  • msgid "Inventory quantity must be zero before you can set this assembly obsolete!"
  • msgstr "在停用此製成品之前, 存貨數量必需為零!"
  • msgid "Inventory quantity must be zero before you can set this part obsolete!"
  • msgstr "停用此項零件之前, 存貨數量必需為零!"
  • msgid "Inventory saved!"
  • msgstr "已儲存存貨!"
  • msgid "Inventory transferred!"
  • msgstr "轉移的存貨!"
  • msgid "Invoice"
  • msgstr "發票"
  • msgid "Invoice Date"
  • msgstr "發票日期"
  • msgid "Invoice Date missing!"
  • msgstr "未指明發票日期!"
  • msgid "Invoice Number"
  • msgstr "發票編號"
  • msgid "Invoice Number missing!"
  • msgstr "未指明發票編號!"
  • msgid "Invoice deleted!"
  • msgstr "已刪除發票!"
  • msgid "Invoices"
  • msgstr "發票"
  • msgid "Is this a summary account to record"
  • msgstr "此為總結科目嗎?"
  • msgid "Item already on pricelist!"
  • msgstr "項目己經在價格清單內!"
  • msgid "Item deleted!"
  • msgstr "已刪除項目!"
  • msgid "Item not on file!"
  • msgstr "沒有此項目的記錄!"
  • msgid "Items"
  • msgstr "項目"
  • msgid "Jan"
  • msgstr "一月"
  • msgid "January"
  • msgstr "一月"
  • msgid "Job"
  • msgstr "工作"
  • msgid "Job Name"
  • msgstr "工作名稱"
  • msgid "Job Number"
  • msgstr "工作編號"
  • msgid "Job Number missing!"
  • msgstr "未指明工作編號!"
  • msgid "Job deleted!"
  • msgstr "工作已被刪除!"
  • msgid "Job saved!"
  • msgstr "工作已被儲存!"
  • msgid "Jobs"
  • msgstr "工作"
  • msgid "Jul"
  • msgstr "七月"
  • msgid "July"
  • msgstr "七月"
  • msgid "Jun"
  • msgstr "六月"
  • msgid "June"
  • msgstr "六月"
  • msgid "LaTeX Templates"
  • msgstr "LaTex 模版"
  • msgid "Labor Code"
  • msgstr "直接人工代碼"
  • msgid "Labor/Overhead"
  • msgstr "直接人工/經常費用"
  • msgid "Language"
  • msgstr "語言"
  • msgid "Language deleted!"
  • msgstr "已刪除語言!"
  • msgid "Language saved!"
  • msgstr "已儲存語言!"
  • msgid "Languages"
  • msgstr "語言"
  • msgid "Languages not defined!"
  • msgstr "不能辨認語言!"
  • msgid "Last Cost"
  • msgstr "上次的成本"
  • msgid "Last Numbers & Default Accounts"
  • msgstr "上一筆編號及預設帳戶"
  • msgid "Lead"
  • msgstr "交付時間"
  • msgid "Leadtime"
  • msgstr "總需時"
  • msgid "Leave host and port field empty unless you want to make a remote connection."
  • msgstr "除非您想要進行遠端連線,否則請將主機及埠號留白。"
  • msgid "Liability"
  • msgstr "負債"
  • msgid "Licensed to"
  • msgstr "授權予"
  • msgid "Line Total"
  • msgstr "總列數"
  • msgid "Link"
  • msgstr "連結"
  • msgid "Link Accounts"
  • msgstr "連結帳戶"
  • msgid "List"
  • msgstr "列表"
  • msgid "List Accounts"
  • msgstr "列出帳號"
  • msgid "List Businesses"
  • msgstr "列出業務"
  • msgid "List Departments"
  • msgstr "列出部門"
  • msgid "List GIFI"
  • msgstr "列出 GIFI"
  • msgid "List Languages"
  • msgstr "列出語言"
  • msgid "List Price"
  • msgstr "列出價"
  • msgid "List Projects"
  • msgstr "列出項目"
  • msgid "List SIC"
  • msgstr "列出標準工業分類代碼"
  • msgid "List Transactions"
  • msgstr "列出所有交易"
  • msgid "List Warehouses"
  • msgstr "列出倉庫"
  • msgid "Lock System"
  • msgstr "系統鎖上"
  • msgid "Lockfile created!"
  • msgstr "已建立上鎖檔案!"
  • msgid "Lockfile removed!"
  • msgstr "已移除上鎖檔案!"
  • msgid "Login"
  • msgstr "登入"
  • msgid "Login name missing!"
  • msgstr "未指明登入名字!"
  • msgid "Logout"
  • msgstr "登出"
  • msgid "Make"
  • msgstr "生產商"
  • msgid "Manager"
  • msgstr "經理"
  • msgid "Mar"
  • msgstr "三月"
  • msgid "March"
  • msgstr "三月"
  • msgid "Marked entries printed!"
  • msgstr "已列印有記號的會計項目!"
  • msgid "Markup"
  • msgstr "漲價"
  • msgid "Maximum"
  • msgstr "最大"
  • msgid "May"
  • msgstr "五月"
  • msgid "May "
  • msgstr "五月"
  • msgid "Memo"
  • msgstr "備忘錄"
  • msgid "Menu Width"
  • msgstr "選擇單寬度"
  • msgid "Message"
  • msgstr "訊息"
  • msgid "Method"
  • msgstr "方法"
  • msgid "Microfiche"
  • msgstr "單片縮影膠片"
  • msgid "Model"
  • msgstr "型號"
  • msgid "Monday"
  • msgstr "星期一"
  • msgid "Month"
  • msgstr "月"
  • msgid "Month(s)"
  • msgstr "月"
  • msgid "Months"
  • msgstr "月"
  • msgid "Multibyte Encoding"
  • msgstr "多字元編碼"
  • msgid "N/A"
  • msgstr "不適用"
  • msgid "Name"
  • msgstr "名稱"
  • msgid "Name missing!"
  • msgstr "未指明名字!"
  • msgid "New Templates"
  • msgstr "新增模版"
  • msgid "New Window"
  • msgstr "新視窗"
  • msgid "Next"
  • msgstr "下一個"
  • msgid "Next Date"
  • msgstr "下一日"
  • msgid "Next Number"
  • msgstr "下一個號碼"
  • msgid "No"
  • msgstr "否"
  • msgid "No Database Drivers available!"
  • msgstr "沒有可用的資料庫驅動程式!"
  • msgid "No Dataset selected!"
  • msgstr "未選定資料集!"
  • msgid "No Employees on file!"
  • msgstr "沒有任何僱員的記錄!"
  • msgid "No Labor codes on file!"
  • msgstr "沒有任何直接人工代碼的記錄!"
  • msgid "No email address for"
  • msgstr "未指明電子郵件地址"
  • msgid "No open Jobs!"
  • msgstr "沒有已開啟的工作"
  • msgid "No open Projects!"
  • msgstr "沒有已開啟的項目"
  • msgid "No."
  • msgstr "編號"
  • msgid "Non-taxable"
  • msgstr "不應課稅"
  • msgid "Non-taxable Purchases"
  • msgstr "不應課稅的採購"
  • msgid "Non-taxable Sales"
  • msgstr "不應課稅的銷售"
  • msgid "Non-tracking Items"
  • msgstr "不用追蹤的項目"
  • msgid "Notes"
  • msgstr "備註"
  • msgid "Nothing entered!"
  • msgstr "沒有任何輸入!"
  • msgid "Nothing selected!"
  • msgstr "沒有選擇任何東西!"
  • msgid "Nothing to delete!"
  • msgstr "沒有可刪除的項目!"
  • msgid "Nothing to print!"
  • msgstr "沒有可列印的項目!"
  • msgid "Nothing to transfer!"
  • msgstr "沒有可轉移的項目!"
  • msgid "Nov"
  • msgstr "十一月"
  • msgid "November"
  • msgstr "十一月"
  • msgid "Number"
  • msgstr "編號"
  • msgid "Number Format"
  • msgstr "數字格式"
  • msgid "Number missing in Row"
  • msgstr "此列中缺少數值"
  • msgid "O"
  • msgstr ""
  • msgid "OH"
  • msgstr "已有存量"
  • msgid "Obsolete"
  • msgstr "停用"
  • msgid "Oct"
  • msgstr "十月"
  • msgid "October"
  • msgstr "十月"
  • msgid "On Hand"
  • msgstr "已有存量"
  • msgid "Open"
  • msgstr "開啟"
  • msgid "Oracle Database Administration"
  • msgstr "Oracle資料庫管理"
  • msgid "Order"
  • msgstr "訂單"
  • msgid "Order Date"
  • msgstr "下單日期"
  • msgid "Order Date missing!"
  • msgstr "未指明下單日期!"
  • msgid "Order Entry"
  • msgstr "下單項目"
  • msgid "Order Number"
  • msgstr "訂單編號"
  • msgid "Order Number missing!"
  • msgstr "未指明訂單編號!"
  • msgid "Order deleted!"
  • msgstr "已刪除訂單!"
  • msgid "Order generation failed!"
  • msgstr "生成訂單失敗!"
  • msgid "Order saved!"
  • msgstr "已儲存訂單!"
  • msgid "Orders generated!"
  • msgstr "已生成訂單!"
  • msgid "Orphaned"
  • msgstr "無主"
  • msgid "Out of balance transaction!"
  • msgstr "不協調交易!"
  • msgid "Out of balance!"
  • msgstr "不平衡!"
  • msgid "Outstanding"
  • msgstr "未收款交易"
  • msgid "PDF"
  • msgstr "PDF"
  • msgid "PO Number"
  • msgstr "採購單編號"
  • msgid "POS"
  • msgstr "銷售點(POS)"
  • msgid "POS Invoice"
  • msgstr "POS發票"
  • msgid "Packing List"
  • msgstr "出貨單"
  • msgid "Packing List Date missing!"
  • msgstr "未指明出貨單日期!"
  • msgid "Packing List Number missing!"
  • msgstr "未指明出貨單編號!"
  • msgid "Packing Lists"
  • msgstr "出貨單"
  • msgid "Paid"
  • msgstr "已付"
  • msgid "Part"
  • msgstr "零件"
  • msgid "Part Number"
  • msgstr "零件編號"
  • msgid "Partnumber"
  • msgstr "零件編號"
  • msgid "Parts"
  • msgstr "零件"
  • msgid "Password"
  • msgstr "密碼"
  • msgid "Password changed!"
  • msgstr "密碼已改!"
  • msgid "Password does not match!"
  • msgstr "密碼不符!"
  • msgid "Passwords do not match!"
  • msgstr "密碼不符!"
  • msgid "Payables"
  • msgstr "應付科目"
  • msgid "Payment"
  • msgstr "付款"
  • msgid "Payment date missing!"
  • msgstr "未指明付款日期!"
  • msgid "Payment posted!"
  • msgstr "已加入付款!"
  • msgid "Payments"
  • msgstr "付款"
  • msgid "Payments posted!"
  • msgstr "已加入付款!"
  • msgid "Payroll Deduction"
  • msgstr "薪金減除額"
  • msgid "Period"
  • msgstr "期間"
  • msgid "Pg Database Administration"
  • msgstr "Pg資料庫管理"
  • msgid "PgPP Database Administration"
  • msgstr "PgPP資料庫管理"
  • msgid "Phone"
  • msgstr "電話號碼"
  • msgid "Pick List"
  • msgstr "揀貨清單"
  • msgid "Pick Lists"
  • msgstr "揀貨清單"
  • msgid "Port"
  • msgstr "埠號"
  • msgid "Port missing!"
  • msgstr "未指明埠號!"
  • msgid "Pos"
  • msgstr "序號"
  • msgid "Post"
  • msgstr "加入"
  • msgid "Post as new"
  • msgstr "當新的加入"
  • msgid "Posted!"
  • msgstr "已加入!"
  • msgid "Posting"
  • msgstr "正加入"
  • msgid "Posting failed!"
  • msgstr "加入失敗!"
  • msgid "Postscript"
  • msgstr "Postscript"
  • msgid "Preferences"
  • msgstr "個人設定"
  • msgid "Preferences saved!"
  • msgstr "個人設定已儲存!"
  • msgid "Prepayment"
  • msgstr "預繳"
  • msgid "Price"
  • msgstr "價格"
  • msgid "Pricegroup"
  • msgstr "價格組別"
  • msgid "Pricegroup deleted!"
  • msgstr "已刪除價格組別!"
  • msgid "Pricegroup missing!"
  • msgstr "未指明價格組別!"
  • msgid "Pricegroup saved!"
  • msgstr "已儲存價格組別!"
  • msgid "Pricegroups"
  • msgstr "所有價格組別"
  • msgid "Pricelist"
  • msgstr "價格清單"
  • msgid "Print"
  • msgstr "列印"
  • msgid "Print and Post"
  • msgstr "列印並加入"
  • msgid "Print and Post as new"
  • msgstr "列印並加入作為新的"
  • msgid "Print and Save"
  • msgstr "列印並儲存"
  • msgid "Print and Save as new"
  • msgstr "列印並儲存作為新的"
  • msgid "Print and post as new"
  • msgstr "列印並加入作為新的"
  • msgid "Print and save as new"
  • msgstr "列印並儲存作為新的"
  • msgid "Printed"
  • msgstr "已列印"
  • msgid "Printer"
  • msgstr "印表機"
  • msgid "Printing"
  • msgstr "正列印"