env editor=vipw vipwしてみた
TRANSCRIPT
env EDITOR=vipw vipw してみた
@yogata
vipw
• ユーザ追加・削除・情報変更する
• vipw is 神
vipw useradd/adduser
メリット Jviが使える 特に無い
デメリット 特にない Luserとaddのどっちが先かわからない
こんなにもすばらしいvipw • 使いたくなってきたはず • でも……viは慣れてないし……
env EDITOR=XXX vipw
vipw と EDITOR変数 • env EDITOR=nano vipw
vipw と EDITOR変数 • env EDITOR=emacs vipw
vipw と EDITOR変数 ( ^o^) env EDITOR=emacs vipw → emacsで開く ( ˘⊖˘) 。o( env EDITOR=vipw vipwしたらどうなるんだ) |Terminal.app| ┗(☋` )┓三 ( ◠‿◠ )☛ そこに気づいてしまったか ▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂うわあああああああ
やってみる
# env EDITOR=vipw vipw
rootになるのを忘れずに
やってみる
調べる • FreeBSD – /usr/src/usr.sbin/vipw/vipw.c – /usr/src/lib/libuMl/pw_uMl.c
– /usr/include/pwd.h
– err周りはここらへん • /usr/src/lib/libc/gen/err.c
vipw.c (1/3) 65 int 66 main(int argc, char *argv[]) 67 { 68 const char *passwd_dir = NULL; 69 int ch, pfd, ad; 70 char *line; 71 size_t len; 72 73 while ((ch = getopt(argc, argv, "d:")) != -‐1) 74 switch (ch) { 75 case 'd': 76 passwd_dir = optarg; 77 break; 78 case '?': 79 default: 80 usage(); 81 } 82 83 argc -‐= opMnd; 84 argv += opMnd;
vipwのオプションは-‐dだけ.それ以外のオプションが来たらusage()する
vipw.c (2/3) 85 86 if (argc != 0) 87 usage(); 88 89 if (pw_init(passwd_dir, NULL) == -‐1) 90 err(1, "pw_init()"); 91 if ((pfd = pw_lock()) == -‐1) { 92 pw_fini(); 93 err(1, "pw_lock()"); 94 } 95 if ((ad = pw_tmp(pfd)) == -‐1) { 96 pw_fini(); 97 err(1, "pw_tmp()"); 98 } 99 (void)close(ad); 100 /* Force umask for parMal writes made in the edit phase */ 101 (void)umask(077);
pw_tmp() ?
pw_lock() ?
pw_init() ?
vipw.c (3/3) 103 for (;;) { 104 switch (pw_edit(0)) { 105 case -‐1: 106 pw_fini(); 107 err(1, "pw_edit()"); 108 case 0: 109 pw_fini(); 110 errx(0, "no changes made"); 111 default: 112 break; 113 } 114 if (pw_mkdb(NULL) == 0) { 115 pw_fini(); 116 errx(0, "password list updated"); 117 }
118 prina("re-‐edit the password file? "); 119 fflush(stdout); 120 if ((line = fgetln(stdin, &len)) == NULL) { 121 pw_fini(); 122 err(1, "fgetln()"); 123 } 124 if (len > 0 && (*line == 'N' || *line == 'n')) 125 break; 126 } 127 pw_fini(); 128 exit(0); 129 }
pw_edit() ?
pw_mkdb() ?
パスワードファイルを再構築に失敗したら,再度ファイルを編集するか確認する
vipw.c まとめ
オプション処理.-‐dだけ
キニナル
pw_init() (1/2) 93 int 94 pw_init(const char *dir, const char *master) 95 { 96 #if 0 97 struct rlimit rlim; 98 #endif 99 100 if (dir == NULL) { 101 strcpy(passwd_dir, _PATH_ETC); 102 } else { 103 if (strlen(dir) >= sizeof(passwd_dir)) { 104 errno = ENAMETOOLONG; 105 return (-‐1); 106 } 107 strcpy(passwd_dir, dir); 108 } 109
-‐dオプションが無ければmaster.passwdファイルがあるディレクトリの初期値として_PATH_ETCを使う(/usr/include/pwd.h)
pw_init() (2/2) 110 if (master == NULL) { 111 if (dir == NULL) { 112 strcpy(masterpasswd, _PATH_MASTERPASSWD); 113 } else if (snprina(masterpasswd, sizeof(masterpasswd), "%s/%s", 114 passwd_dir, _MASTERPASSWD) > (int)sizeof(masterpasswd)) { 115 errno = ENAMETOOLONG; 116 return (-‐1); 117 } 118 } else { 119 if (strlen(master) >= sizeof(masterpasswd)) { 120 errno = ENAMETOOLONG; 121 return (-‐1); 122 } 123 strcpy(masterpasswd, master); 124 }
master.passwdのファイル名を指定することができるが,vipw.cからはNULLなので_PATH_MASTERPASSWD (/usr/include/pwd.h)を使う
vipw.c まとめ
オプション処理.-‐dだけ
パスワードファイルの場所を探す
pw_lock() 166 int 167 pw_lock(void) 168 { 169 170 if (*masterpasswd == '\0') 171 return (-‐1); <snip> 179 for (;;) { 180 struct stat st; 181 182 lockfd = open(masterpasswd, O_RDONLY, 0); 183 if (lockfd < 0 || fcntl(lockfd, F_SETFD, 1) == -‐1) 184 err(1, "%s", masterpasswd); 185 /* XXX vulnerable to race condiMons */ 186 if (flock(lockfd, LOCK_EX|LOCK_NB) == -‐1) { 187 if (errno == EWOULDBLOCK) { 188 errx(1, "the password db file is busy"); 189 } else { 190 err(1, "could not lock the passwd file: "); 191 } 192 } <snip>
ロックする
master.passwdファイルを開いて
vipw.c まとめ
オプション処理.-‐dだけ
パスワードファイルの場所を探す
パスワードファイルをロックする
pw_tmp() (1/2) 213 int 214 pw_tmp(int mfd) 215 { 216 char buf[8192]; 217 ssize_t nr; 218 const char *p; 219 int ad; 220 221 if (*masterpasswd == '\0') 222 return (-‐1); 223 if ((p = strrchr(masterpasswd, '/'))) 224 ++p; 225 else 226 p = masterpasswd; 227 if (snprina(tempname, sizeof(tempname), "%.*spw.XXXXXX", 228 (int)(p -‐ masterpasswd), masterpasswd) >= (int)sizeof(tempname)) { 229 errno = ENAMETOOLONG; 230 return (-‐1); 231 } 232 if ((ad = mkstemp(tempname)) == -‐1) 233 return (-‐1);
master.passwdファイルがあるディレクトリに一時ファイルpw.XXXXXX (Xは乱数に置き換え)を作成.
pw_tmp() (2/2) 234 if (mfd != -‐1) { 235 while ((nr = read(mfd, buf, sizeof(buf))) > 0) 236 if (write(ad, buf, (size_t)nr) != nr) 237 break; 238 if (nr != 0) { 239 unlink(tempname); 240 *tempname = '\0'; 241 close(ad); 242 return (-‐1); 243 } 244 } 245 return (ad); 246 }
master.passwdの中身を一時ファイルにコピー
vipw.c まとめ
オプション処理.-‐dだけ
パスワードファイルの場所を探す
パスワードファイルをロックする
パスワードファイルと同じディレクトリに作業用ファイルを作って,中身をコピーする
pw_edit() (1/4) 288 int 289 pw_edit(int notsetuid) 290 { <snip> 297 if ((editor = getenv("EDITOR")) == NULL) 298 editor = _PATH_VI; 299 if (stat(tempname, &st1) == -‐1) 300 return (-‐1); <snip>
パスワードファイルを編集するエディタを設定.EDITOR変数の設定が無ければviを設定
pw_edit() (2/4) 309 switch ((editpid = fork())) { 310 case -‐1: 311 return (-‐1); 312 case 0: 313 sigacMon(SIGINT, &sa_int, NULL); 314 sigacMon(SIGQUIT, &sa_quit, NULL); 315 sigprocmask(SIG_SETMASK, &oldsigset, NULL); 316 if (notsetuid) { 317 (void)setgid(getgid()); 318 (void)setuid(getuid()); 319 } 320 errno = 0; 321 execlp(editor, basename(editor), tempname, (char *)NULL); 322 _exit(errno); 323 default: 324 /* parent */ 325 break; 326 }
master.passwdをコピーした一時ファイル(tempname)を引数にしてエディタ(editor)を開く.
104 switch (pw_edit(0)) { 105 case -‐1: 106 pw_fini(); 107 err(1, "pw_edit()"); <snip> 113 }
vipw.c
後述
pw_edit() (3/4) 327 for (;;) { 328 if (waitpid(editpid, &pstat, WUNTRACED) == -‐1) { 329 if (errno == EINTR) 330 conMnue; 331 unlink(tempname); 332 editpid = -‐1; 333 break; 334 } else if (WIFSTOPPED(pstat)) { 335 raise(WSTOPSIG(pstat)); 336 } else if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0) { 337 editpid = -‐1; 338 break; 339 } else { 340 unlink(tempname); 341 editpid = -‐1; 342 break; 343 } 344 }
execlp()が正常終了したら一時ファイルを残して終了する
execlp()が異常終了したら一時ファイルを削除して終了する
エラーだったらファイルを削除して終了する
waitpid()でエラーだったり,execlp()が異常終了したらファイルを削除する
pw_edit() (4/4) 345 sigacMon(SIGINT, &sa_int, NULL); 346 sigacMon(SIGQUIT, &sa_quit, NULL); 347 sigprocmask(SIG_SETMASK, &oldsigset, NULL); 348 if (stat(tempname, &st2) == -‐1) { 349 return (-‐1); 350 } 351 return (st1.st_mMme != st2.st_mMme); 352 }
execlp()が正常終了したら一時ファイルを残して終了する
104 switch (pw_edit(0)) { 105 case -‐1: 106 pw_fini(); 107 err(1, "pw_edit()"); 108 case 0: 109 pw_fini(); 110 errx(0, "no changes made"); 111 default: 112 break; 113 }
変更がない時
変更がある時
vipw.c
waitpid()でエラーだったりexeclp()で不正終了していたらtempnameファイルは無いのでstat()が-‐1になりreturn(-‐1)
EDITORの終了を失敗したり,execlp()でEDITORが不正終了した場合
後述のアレ
pw_edit()のところで
execlp(editor, basename(editor), tempname, (char *)NULL); ↓ execlp(“vipw” “vipw”, “pw.XXXXX”, (char *)NULL);
こうなって,結局のところ
# vipw pw.XXXXX な感じになる
そういえば • vipwコマンドに”pw.XXXXX”引数は不正なのでusage()
while ((ch = getopt(argc, argv, "d:")) != -‐1) switch (ch) {
… default: usage(); }
# env EDITOR=vipw vipw usage: vipw [-‐d directory] vipw: pw_edit(): No such file or directory
つまりこうなる
EDITORが不正終了したので 327 for (;;) { 328 if (waitpid(editpid, &pstat, WUNTRACED) == -‐1) { 329 if (errno == EINTR) 330 conMnue; 331 unlink(tempname); 332 editpid = -‐1; 333 break; 334 } else if (WIFSTOPPED(pstat)) { 335 raise(WSTOPSIG(pstat)); 336 } else if (WIFEXITED(pstat) && WEXITSTATUS(pstat) == 0) { 337 editpid = -‐1; 338 break; 339 } else { 340 unlink(tempname); 341 editpid = -‐1; 342 break; 343 } 344 }
execlp()が不正終了したら一時ファイルを削除して終了する
pw_edit()
pw_edit()を振り返って 345 sigacMon(SIGINT, &sa_int, NULL); 346 sigacMon(SIGQUIT, &sa_quit, NULL); 347 sigprocmask(SIG_SETMASK, &oldsigset, NULL); 348 if (stat(tempname, &st2) == -‐1) { 349 return (-‐1); 350 } 351 return (st1.st_mMme != st2.st_mMme); 352 }
104 switch (pw_edit(0)) { 105 case -‐1: 106 pw_fini(); 107 err(1, "pw_edit()"); 108 case 0: 109 pw_fini(); 110 errx(0, "no changes made"); 111 default: 112 break; 113 }
vipw.c
execlp()で不正終了していたらtempnameファイルは無いので stat()が-‐1になりreturn(-‐1)
EDITORの終了を失敗したり,execlp()でEDITORが不正終了した場合
# env EDITOR=vipw vipw usage: vipw [-‐d directory] vipw: pw_edit(): No such file or directory
vipw.c まとめ
オプション処理.-‐dだけ
パスワードファイルの場所を探す
パスワードファイルをロックする
パスワードファイルと同じディレクトリに作業用ファイルを作って,中身をコピーする
EDITORで指定したコマンドに作業用ファイルを渡したり, コマンドが異常終了したら作業ファイルを消したり, 作業ファイルの更新時間を確認したり, 作業ファイルが消されてたら更新時間を確認できないのでエラーを返したり, する.
(参考) env EDITOR=nano vipwのとき
pw.XXXXX の部分
pw_mkdb() • 省略
まとめ • vipwは内部でexeclp()を使ってEDITORを実行し,引
数に作業用ファイルを渡す • 1つ目のエラーはEDITOR=vipwの時にvipwコマンド
が-‐d以外の引数を許容しないエラー • 2つ目のエラーは,1つ目のエラーに起因して作業用
ファイルを消し,その後の処理で作業用ファイルを見に行こうとするが存在しないので出るエラー
# env EDITOR=vipw vipw usage: vipw [-‐d directory] vipw: pw_edit(): No such file or directory