Software /
code /
clix
File
clix.lua @ 167:57bb6e03d239
clix.raw: Update sandboxing to use util.envload and work with Lua 5.2+
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Thu, 02 Mar 2023 18:01:05 +0100 |
parent | 166:b0c586241224 |
child | 168:75e8ca131178 |
line wrap: on
line source
#!/usr/bin/env lua -- Clix -- Command-line XMPP -- Copyright (C) 2008-2010 Matthew Wild -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local verse = require "verse".init "client" -- Global to allow commands to add to it short_opts = { v = "verbose", q = "quiet", t = "to", f = "from", e = "type", a = "account", p = "password", r = "resource", o = "presence", c = "chatroom", i = "interactive", f = "file" } if #arg < 1 then print("Command Line XMPP, available commands:"); for module in pairs(package.preload) do if module:match("^clix%.") then local m = require(module); io.write("\t", module:gsub("^clix%.", ""), ": "); m({ short_help = true }, {}); end end return 0; end local accounts = { default = {} }; local current_account; local config_path = (os.getenv("XDG_CONFIG_HOME") or (os.getenv("HOME").."/.config")); local config_file, err = io.open(config_path.."/.clixrc") or io.open(config_path.."/.clix"); if not config_file then print("Unable to open config file... looked for "..config_path.."/.clixrc"); if err then print(err); end os.exit(1); end for line in config_file:lines() do line = line:match("^%s*(.-)%s*$"); if line:match("^%[") then current_account = line:match("^%[(.-)%]"); accounts[current_account] = {}; if not current_account then -- This is the first defined account accounts.default = accounts[current_account]; end elseif current_account then local k,v = line:match("^(%w+)%s*[:=]%s*(.+)$"); if k then accounts[current_account or "default"][k] = v; end end end local function read_password() io.write("Password: ") local stty_ret = os.execute("stty -echo 2>/dev/null"); if stty_ret ~= 0 then io.write("\027[08m"); -- ANSI 'hidden' text attribute end local ok, pass = pcall(io.read, "*l"); if stty_ret == 0 then os.execute("stty sane"); else io.write("\027[00m"); end io.write("\n"); if ok then return pass; end end function clix_connect(opts, on_connect, plugins) local account = accounts[opts.account or "default"]; if not (account and account.jid) then io.stderr:write("The specified account (", opts.account or "default", ") wasn't found in the config file\n"); return nil; end verse.set_log_handler(io.stderr, opts.quiet and {} or not opts.verbose and {"info", "warn", "error"}); local conn = verse.new(verse.new_logger("clix")); if opts.raw then conn:hook("incoming-raw", print, 1000); conn:hook("outgoing-raw", print, 1000); end for _, plugin in ipairs(plugins or {}) do conn:add_plugin(plugin); end conn:hook("authentication-failure", function (err) conn:error("Authentication failure ("..(err.condition or "unknown error")..")"..(err.text and (": "..err.text) or "")); conn:close(); end); conn:hook("ready", function () conn:info("Connected as %s", conn.jid); if opts.chatroom then conn:send(verse.presence{to=opts.to.."/"..(opts.nick or "clix")}); end if opts.presence then conn:send(verse.presence()); end return on_connect(conn); end); conn:hook("bind-failure", function (err) conn:error("Resource binding failure ("..(err.condition or "unknown error")..")"..(err.text and (": "..err.text) or "")); conn:close(); end); conn:hook("disconnected", function (info) if info and info.reason and info.reason ~= "stream closed" then conn:warn("Disconnecting: %s", tostring(info.reason)); end verse.quit(); end); -- Optional config parameters conn.connect_host = account.address; conn.connect_port = account.port; if not account.password and not (account.clientkey and account.serverkey) and opts.interactive then account.password = read_password() end local clientkey, serverkey = account.clientkey, account.serverkey; if clientkey and serverkey then local hex = require "util.hex"; clientkey = hex.from(clientkey); serverkey = hex.from(serverkey); elseif clientkey or serverkey then conn:warn("Only one of 'clientkey' and 'serverkey' available, both reqired for SCRAM") clientkey, serverkey = nil, nil; end -- Connect! conn:connect_client(account.jid, account.password, clientkey, serverkey); -- COMPAT: Tigase discards stanzas sent at the same time as </stream:stream> local _real_close = conn.close; function conn:close() conn:debug("Delaying close..."); local function close_conn() local function do_close() if _real_close then conn:debug("Closing now..."); local close = _real_close; _real_close = nil; close(conn); end end local close_delay = tonumber(opts.close_delay) or 0; if close_delay > 0 then verse.add_task(close_delay, do_close); else do_close(); end end if conn.conn:bufferlen() == 0 then close_conn(); else conn:hook("drained", close_conn); end end -- /COMPAT if type(opts.resource) == "string" then conn.resource = opts.resource; end local ok, ret = pcall(verse.loop); if not ok and not ret:match("interrupted!$") then io.stderr:write("Fatal error: ", ret, "\n"); return 1; end return err or 0; end local opts = {}; local command, args_handled_up_to; for i, opt in ipairs(arg) do if opt:match("^%-") and opt ~= "--" then local name = opt:match("^%-%-?([^%s=]+)()") name = (short_opts[name] or name):gsub("%-+", "_"); if name:match("^no_") then name = name:sub(4, -1); opts[name] = false; else opts[name] = opt:match("=(.*)$") or true; end elseif not command then command = arg[i]; args_handled_up_to = i-1; else args_handled_up_to = i-1; break; end end -- Remove all the handled args from the arg array for n=((args_handled_up_to > 0) and args_handled_up_to or #arg),1,-1 do table.remove(arg, n); end local ok, m = pcall(require, "clix."..command); if not ok then local is_debug; for i=1,#arg do if arg[i] == "--debug" then is_debug = true; break; end end print("Error running command '"..command..(is_debug and "" or "' (run with --debug to see full error)")); if is_debug then print(m); end return 1; end if type(m) ~= "function" then print(command.." is not a valid command"); return 1; end return m(opts, arg) or 0;