Linuxのuseraddコマンドって
ちょいと、お客さんの案件で、数字のみで構成されるユーザIDをWindows,Unix(Solaris)混在環境で使用したいという話があって、LDAP ManagerでUnix連携してみたら数字だけのユーザIDを受け付けてくれなかった。
どうして?って事で、useraddコマンドのソースを覗いてみることにした。
まずは、useraddってどのパッケージに含まれているのか?
# rpm -qf /usr/sbin/useradd shadow-utils-4.0.3-61.RHEL4
# rpm -ivh shadow-utils-4.0.3-60.RHEL4.src.rpm
でインストール(あってるのかな?昔は--rebuildとかしていたみたいだけど)
# cd /usr/src/redhat/SOURCES/ # bunzip2 shadow-4.0.3.tar.bz2 # tar -xvf shadow-4.0.3.tar
でソースを展開
useraddコマンドの本体は、shadow-4.0.3/src/useradd.cにある。
この中で、
user_name = argv[optind]; if (!check_user_name (user_name)) { fprintf (stderr, _("%s: invalid user name '%s'\n"), Prog, user_name); exit (E_BAD_ARG); }
として、check_user_nameから0が帰ってくるとエラーと判断するらしい。
で、check_user_nameはshadow-4.0.3/libmisc/chkname.cにある。
int check_user_name(const char *name) { #if HAVE_UTMPX_H struct utmpx ut; #else struct utmp ut; #endif /* * User names are limited by whatever utmp can * handle (usually max 8 characters). */ if (strlen(name) > sizeof(ut.ut_user)) return 0; return good_name(name); }
となっていて、実際の文字種類の確認はgood_nameで実施している。
good_nameでは、
static int good_name(const char *name) { /* * User/group names must match [a-z_][a-z0-9_-]* */ if (!*name || !((*name >= 'a' && *name <= 'z') || *name == '_')) return 0; while (*++name) { if (!((*name >= 'a' && *name <= 'z') || (*name >= '0' && *name <= '9') || *name == '_' || *name == '-' || (*name == '$' && *(name+1) == NULL))) return 0; } return 1; }
となっていて、先頭1文字は[a-z_]で、それ以降は[a-z0-9_\-]、最後の1文字になら$を含める仕様になっているようだ。
ちょいプロを書いて確認してみると
#include <stdio.h> static int good_name(const char *name) { /* * User/group names must match [a-z_][a-z0-9_-]* */ if (!*name || !((*name >= 'a' && *name <= 'z') || *name == '_')) return 0; while (*++name) { if (!((*name >= 'a' && *name <= 'z') || (*name >= '0' && *name <= '9') || *name == '_' || *name == '-' || (*name == '$' && *(name+1) == NULL))) return 0; } return 1; } main(){ char *name1 = "12345"; char *name2 = "a12345"; char *name3 = "1a2345"; char *name4 = "_a12345"; char *name5 = "a12345$"; char *name6 = "a1234$5"; char *name7 = "a1234%5"; char *name8 = "_12345"; good_name( name1 ); printf ( "%s;ret %d\n",name1,good_name( name1 )); printf ( "%s;ret %d\n",name2,good_name( name2 )); printf ( "%s;ret %d\n",name3,good_name( name3 )); printf ( "%s;ret %d\n",name4,good_name( name4 )); printf ( "%s;ret %d\n",name5,good_name( name5 )); printf ( "%s;ret %d\n",name6,good_name( name6 )); printf ( "%s;ret %d\n",name7,good_name( name7 )); printf ( "%s;ret %d\n",name8,good_name( name8 )); }
# ./a.out 12345;ret 0 a12345;ret 1 1a2345;ret 0 _a12345;ret 1 a12345$;ret 1 a1234$5;ret 0 a1234%5;ret 0 _12345;ret 1
となる。
が、実際にuseradd 11111とやっても正常に動作するし、ログインも出来ちゃうのはなぜ?
よーっく見てみると
/usr/src/redhat/SOURCES/配下には沢山のパッチが並んでいて、shadow-4.0.3-goodname.patchというのがある。
開いてみると
--- shadow-4.0.3/libmisc/chkname.c.goodname 2002-01-10 08:04:34.000000000 -0500 +++ shadow-4.0.3/libmisc/chkname.c 2004-12-03 16:56:02.931628451 -0500 @@ -22,16 +22,24 @@ good_name(const char *name) { /* - * User/group names must match [a-z_][a-z0-9_-]* + * User/group names must match gnu e-regex: + * [a-zA-Z0-9_.][a-zA-Z0-9_-.]{0,30}[a-zA-Z0-9_.$-]? + * + * as a non-POSIX, extension, allow "$" as the last char for + * sake of Samba 3.x "add machine script" */ - if (!*name || !((*name >= 'a' && *name <= 'z') || *name == '_')) + if (!*name || !((*name >= 'a' && *name <= 'z') + || (*name >= 'A' && *name <= 'Z') + || (*name >= '0' && *name <= '9') + || *name == '_' || *name == '.')) return 0; while (*++name) { - if (!((*name >= 'a' && *name <= 'z') || - (*name >= '0' && *name <= '9') || - *name == '_' || *name == '-' || - (*name == '$' && *(name+1) == NULL))) + if (!( (*name >= 'a' && *name <= 'z') + || (*name >= 'A' && *name <= 'Z') + || (*name >= '0' && *name <= '9') + || *name == '_' || *name == '.' || *name == '-' + || (*name == '$' && *(name + 1) == '\0'))) return 0; } @@ -48,10 +56,9 @@ #endif /* - * User names are limited by whatever utmp can - * handle (usually max 8 characters). + * User names are limited by whatever utmp can handle. */ - if (strlen(name) > sizeof(ut.ut_user)) + if (strlen(name) + 1 > sizeof(ut.ut_user)) return 0; return good_name(name);
となっていて、どうやらsamba対応のために、かなり拡張されているようだ。
これで数字だけのユーザが出来てしまう理由がわかったよ。
良かった(^^;