標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

OJTばかりやって

OJTばかりやって、座学や書籍で理論をちゃんとやらんから、妙な社内標準・社内フレームワーク・社内ローカルルールが出来て、他所で通用しない社員ばかりになるんだな

https://twitter.com/zetamatta/status/881672613889679360

四国の僧侶の先生が言ってた「瞑想ばかりやって、仏法の勉強をしないと、自分の超体験だけが標準になって、妙な新興宗教を始める」のと同じだと思う

https://twitter.com/zetamatta/status/881674952562270213

readline ライブラリ

nyagos 以外からでも簡単に使えるよう、いろいろがんばった

package main

import (
    "context"
    "fmt"
    "github.com/zetamatta/nyagos/readline"
)

func main() {
    editor := readline.Editor{
        Default: "InitialValue",
        Cursor:  3,
    }
    text, err := editor.ReadLine(context.Background())

    if err != nil {
        fmt.Printf("ERR=%s\n", err.Error())
    } else {
        fmt.Printf("TEXT=%s\n", text)
    }
}

よかったら使ってね

俺は荒木飛呂彦先生は…

ジョジョ第三部の途中で亡くなっていて、今描いているのは二代目ではないかと思っている。

  • 画風が変わっている
  • 登場人物が主人公をジョジョと全く呼ばなくなる
  • このあたりから各話を適当に入れ替えても成り立つようなストーリー構成へ
  • 先生自身が若返っている

https://twitter.com/zetamatta/status/877691788986679296

ちょっと前に「有名漫画家が事故で死んで、編集者とチーフアシスタントがその死を隠蔽しつつ、 続きを描いて、その巨額の財産を奪おうとする」という漫画があったなぁ。名前忘れたが

https://twitter.com/zetamatta/status/877692165232533504

Windows における su(sudo) 事情考察

su (sudo)とは

suというと、通常は UNIX(Linux)で、ログアウトせずにユーザを切り替えるコマンドを指し、主に root (管理ユーザ)に切り替える用途に使われることが多い。そして、sudo は1コマンドだけユーザを切り替えて実行するコマンドだ。

最近、Windows でも su(sudo)的なコマンドが現れたが、これは常に管理ユーザで使われがちの Windows においては、ユーザを切り替えるというよりも、管理者権限を獲得するためのコマンドという位置づけとなっていることが多い。

本文書では、管理者権限を得てコマンドを実行する操作について、手法を紹介してみたい。

管理者権限があるかどうかのチェック

C++

advapi32.dll の OpenProcessTokenGetTokenInformation という API を組み合わせてチェックするという方法がある。詳しく解説しているページはこち

自分でも Go 言語で実装してみた。(nyagos'Lua からはnyagos.elevated()で利用できる)

バッチファイル

net session >nul 2>&1」は管理者権限でないと ERRORLEVEL=2 で失敗するということを利用して判別できる。このテクニックは「Office 365のデスクトップアプリケーションをバッチファイルで修復」で用いられていたが、なかなか手軽でよいと思われる。

管理者権限を得るには

C++

WindowsAPI で管理者権限を得るのは ShellExecute になる。

Windows ではプロセスを起動する有名どころの API としては CreateProcess と ShellExecute の2種類がある。CreateProcess は「普通」にプロセスを起動する API で、パラメータがやたら多くて難しい(← いいすぎ)他は特筆すべきことはない。たいていの言語の子プロセス起動ライブラリはこれを使用している。一方、ShellExecute はデスクトップからアイコンをクリックしたのと同じような形でコマンドを実行するような API である。

ShellExecute のパラメータは CreateProcess よりむしろ簡単で、次のような形となる。

https://msdn.microsoft.com/ja-jp/library/cc422072.aspx より引用:

HINSTANCE ShellExecute(
    HWND hwnd,              // 親ウィンドウのハンドル
    LPCTSTR lpVerb,         // 操作
    LPCTSTR lpFile,         // 操作対象のファイル
    LPCTSTR lpParameters,   // 操作のパラメータ
    LPCTSTR lpDirectory,    // 既定のディレクトリ
    INT nShowCmd            // 表示状態
);

lpVerb に入れる文字列で L"open" を与えると、普通の起動になるが、それを L"runas" と変えると、管理者として実行となる。これを利用した su/sudo はかなり多く、NYAGOS に内蔵している su / sudo もこれである

.NET Framework

Dim pinfo1 As New System.Diagnostics.ProcessStartInfo()
pinfo1.Verb = "RunAs" '*** This is for UAC mode ***
' 中略
Dim process As System.Diagnostics.Process =
       System.Diagnostics.Process.Start(pinfo1)

これを利用した su として以前 wouldyou.exeというものを作ったが、後述の powershell を使った方法の方で用が足りるので、今はほとんど使っていない。

バッチファイル&PowerShell

.NET Framework と同じ方法がとれるが、COM を使った方法の方がお手軽。COM を使っているので、当然ながら JScript/VBScript でも同じことができる。が、起動ロゴが出たりするので、やはり、PowerShell が最適解だろう。

@setlocal
@if not "%1" == "" @set "ARG=/c %*"
powershell "(New-Object -Com Shell.Application).ShellExecute('cmd',$Env:ARG,'','runas')"
@endlocal

これのすごくよいところは1行コピペしてしまえば、それだけで用が足りる点だ (バッチファイル1枚の中で完結する)。

真の sudo を目指して

これらを利用して、su/sudo 的なものは比較的簡単に実現できる。自分はこれで満足していたが、コマンドプロンプトで実行する際、管理者権限を得たコマンドラインは別のコンソール窓になってしまうという点があり、これを使いにくく感じる向きも多いようだ。

ということで、それを解決した sudo も登場している。

実装の仕方を見ると、管理者権限で動くプロセスを別に立ち上げて、そちらとパイプラインを結んで、それまで使っていたコンソールに管理者権限で実行しているプロセスの標準入出力を引用しているようだ(← あまりソースを真剣に読んでいない)

なお、これらを NYAGOS で動かしたい時は NYAGOS内蔵の sudo を無効にするため、

  • ~\.nyagosnyagos.alias.sudo="sudo.exe"

もしくは

  • ~\_nyagosalias sudo=sudo.exe

としよう(.exeをつければ内蔵コマンドとみなされない)。nyagos の sudo を使わないのは、それが新しいコンソールを起動してしまうためだ(一応、廃止も検討している)

以上

expect for Command Prompt by GopherLua

コマンドプロンプト向けの expect を Go 言語で作った。

特徴

  • スクリプトLua で書く。GopherLua を使ったので、lua53.dll は不要
  • 画面のプロンプトを待つ except() 関数は、本家だと標準出力・標準エラーを監視するが、こちらは「現在カーソルがある行とその上の行」を0.1秒間隔で監視させている
    • git付属のOpenSsh のパスワード入力の際のプロンプトの出力先が標準出力・標準エラー出力のどちらでもないため、本方式を採用した。
    • kernel32.dll の ReadConsoleOutput という API を使ったが、これ、バッファの確保の仕方のルールが分からず、えらく苦労した。
  • コマンドに入力内容を送信する send() 関数は、本家だと標準入力に文字列を流し込むが、こちらはキーボードが叩かれたのコンソールイベントを発生させている

使用例

実装した関数は spawnexpectsendだけなんだけど、Lua なのでいろいろ柔軟なことが出来る。

if spawn([[c:\Program Files\Git\usr\bin\ssh.exe]],"foo@example.com") then
    expect("password:")
    send("PASSWORD\r")
    expect("~]$")
    send("exit\r")
end

作った感想

  • UNIX/Linux サーバ相手に本気で expect とかやりたい人は、TeraTerm マクロを使う
  • Windows での動作の自動化は Command Prompt だけではどうにもならない場合が多い。

使ってもらえるシーンないな!(まだ技術の無駄遣い)

git for Windows 付属の ssh でのログインを自動化したい

Linux なら expect というツールがあるけど、Windows の場合、どうしたらいいんだろう」などと思って、API を調べていたら、WriteConsoleInput などというコンソールイベントを「捏造」できる API があるのを発見。

ちょっと、ツールを作ってみた。

package main

import (
    "os"
    "syscall"
    "unsafe"
)

var kernel32 = syscall.NewLazyDLL("kernel32")
var writeConsoleInput = kernel32.NewProc("WriteConsoleInputW")

type inputRecordT struct {
    eventType         uint16
    _                 uint16
    bKeyDown          int32
    wRepeartCount     uint16
    wVirtualKeyCode   uint16
    wVirtualScanCode  uint16
    unicodeChar       uint16
    dwControlKeyState uint32
}

type Handle syscall.Handle

func NewHandle() (Handle, error) {
    handle, err := syscall.Open("CONIN$", syscall.O_RDWR, 0)
    return Handle(handle), err
}

const (
    KEY_EVENT = 1
)

func (handle Handle) WriteRune(c rune) uint32 {
    records := []inputRecordT{
        inputRecordT{
            eventType:         KEY_EVENT,
            bKeyDown:          1,
            unicodeChar:       uint16(c),
            dwControlKeyState: 0,
        },
        inputRecordT{
            eventType:         KEY_EVENT,
            bKeyDown:          0,
            unicodeChar:       uint16(c),
            dwControlKeyState: 0,
        },
    }
    var count uint32
    writeConsoleInput.Call(uintptr(handle), uintptr(unsafe.Pointer(&records[0])), 2, uintptr(unsafe.Pointer(&count)))
    return count
}

func (handle Handle) WriteString(s string) {
    for _, c := range s {
        handle.WriteRune(c)
    }
}

func main() {
    console, err := NewHandle()
    if err != nil {
        println(err.Error())
        return
    }
    for _, s := range os.Args[1:] {
        console.WriteString(s)
        console.WriteRune('\r')
    }
}

これをビルドして typekeyas.exe というコマンドを作って「typekeyas "dir /w"」などと打つと、あたかも直後にコマンドプロンプトに対して自分が「dir /w」と打ったかのようにディレクトリが表示される(いや、引数の中で打ってはいるんだけど)

これを使ってパスワードを打たせてみよう(以下、一部伏字)

$ typekeyas (PASSWORD) & "c:\Program Files\Git\usr\bin\ssh.exe" XXXXX@XXXXX.org
XXXXX@XXXXX.org's password:
Last login: Wed Jun  7 22:09:15 2017 from XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
FreeBSD 9.1-RELEASE-p24 (SAKURA17) #0: Thu Feb  5 10:03:29 JST 2015

Welcome to FreeBSD!

nyagos の場合はコマンド区切り文字の「&」を「;」にすればよいが。。。 これパスワードがヒストリに残ってしまうからすごく危険なんだな!

で、「これ画面の出力も拾えれば、完全な expect が出来るのでは?」とか思ったが、最初の XXXXX@XXXXX.org's passwordという行だけに限って標準出力・標準エラー出力ともに出ていない(他は出てる)。コンソール(CONOUT$)に直接出しているんでしょうかね…

Thunderbird で External Editor(外部エディター起動アドオン) が動かなくなった俺達は

(2019.03.04 追記)

最新の External Editor は GitHub からダウンロードできるようです。

Thunderbird 66 向け(v2.0.0-beta)と、Thunderbird 60 向け(v1.0.5-rc0)向けは違うので、ご注意ください。

(本文)

エディターが起動していないのに、エディターの終了待ちでハングしてしまう。 どうやら Thunderbird が52へアップデートしたタイミングで、動かなくなったアドオンがいろいろ出ているようだ。 (参考:Thunderbird 52.0 Release - とりかごとなり。

ググってみたところ、External Editor のサイトの掲示板( http://globs.org/articles.php?lng=en&pg=2 )に下記の書き込みがあった。

どうやら、原作者ではなく、ユーザで修正版を作った方がいらっしゃるようだ。ありがたや。

インストールされている External Editor を削除して、リンク先の exteditor_tb52_v101.xpi を アドオン画面にドラッグ&ドロップしたところ、 External Editor が前のように起動されるようになった。よしよし