Date: Mon, 27 May 1996 14:04:28 -0700 (MST) From: Terry Lambert <terry@lambert.org> To: regnauld@tetard.hsc.fr (Philippe Regnauld) Cc: hackers@freebsd.org Subject: Re: Adduser program in C Message-ID: <199605272104.OAA08998@phaeton.artisoft.com> In-Reply-To: <199605251733.TAA07641@tetard.hsc.fr> from "Philippe Regnauld" at May 25, 96 07:33:57 pm
next in thread | previous in thread | raw e-mail | index | archive | help
> PS: I have in mind creating a series of X admin tools with Perl/Tk
> for FreeBSD. The main advantages are: easy to maintain, and easy
> to adapt to other Unices (i.e.: Linux, etc.)
Please do not do this.
Try to think, instead, of a gramatical UI construct that can be used
to build a (G)UI for any program meeting certain criteria.
Specifically, it's easy to envision a user administration program that
can operate in three modes:
1) Command line:
% uadmin show user terry verbose
NAME 'Terry Lambert'
USER terry
ID 501
GROUP 20 (staff)
+GROUP 0 (wheel)
+GROUP 552 (ncvs)
HOME /home/terry
SHELL /bin/csh
EXPIRE PASSWORD NEVER
EXPIRE ACCOUNT NEVER
%
2) Interactive:
% uadmin
UADMIN> show user terry
NAME 'Terry Lambert'
USER terry
ID 501
GROUP 20 (staff)
HOME /home/terry
SHELL /bin/csh
EXPIRE PASSWORD NEVER
EXPIRE ACCOUNT NEVER
UADMIN> EXIT
%
3) Subprocess of GUI via IPC:
,--------------------------------.
| SHOW USER |
,--------------------------------.
| |
| Username: ____________________ |
| |
| |
| > OK < >Cancel< >Browse...< |
`--------------------------------'
Clearly,
1) arguments invoke as command line
2) no arguments invoke as interarctive or UI driven
a) interactive if stdin stats as tty
b) UI driven if stdin stats as non-tty
This implies a command parse hierarchy for the CLI and command line
versions of the program:
1.0 Abstract
The uadmin program implements a user administration tool.
The uadmin tool is a CLI (command line interface) program
which may be used to administer:
o allowable shells
o allowable groups
o user accounts, including
o user ID
o login group
o additional group memebrship
o passwords, including
o format requirements
o password aging
o defaults for the uadmin program, including
o skeleton file directory
o use of skeleton files
o password assignment
o default shell for new users
o default group for new users
o default minimum user ID for new users
o default minimum group ID for new groups
o status reporting, including
o next available user ID greater than or
equal to the minimum user ID
o GUI interface definition
etc.
Obviously, if the GUI interface definition and the user input
used a commit/cancel interaction, then it would be possible to
implement dialog interactions for user input in the UI case:
UADMIN> BEGIN ADD USER
UADMIN] NAME 'Terry Lambert'
UADMIN] USER terry
UADMIN] ID 501
UADMIN] GROUP 20
UADMIN] +GROUP 0
UADMIN] +GROUP 552
UADMIN] HOME /home/terry
UADMIN] SHELL /bin/csh
UADMIN] EXPIRE PASSWORD NEVER
UADMIN] EXPIRE ACCOUNT NEVER
UADMIN] END ACCEPT
UADMIN>
Each time the 'name' field changed, for instance:
UADMIN> BEGIN ADD USER
UADMIN] NAME 'Terry'
UADMIN] USER terry
UADMIN] ID 501
UADMIN] GROUP 20
UADMIN] +GROUP 0
UADMIN] +GROUP 552
UADMIN] NAME 'Lambert'
UADMIN] HOME /home/terry
UADMIN] SHELL /bin/csh
UADMIN] NAME 'Terry Lambert'
UADMIN] EXPIRE PASSWORD NEVER
UADMIN] EXPIRE ACCOUNT NEVER
UADMIN] END ACCEPT
UADMIN>
And then the current record is committed/uncommitted as an agregate:
UADMIN] END ACCEPT
UADMIN] END CANCEL
The point being that you could compartmentalize a dialog that
way rather easily, such that the GUI program would not need to
have specific knowledge of what command line utility it was to
be used for.
Pretty obviously, the same GUI or text UI (both should be provided)
could be used to do disk partitioning, or whatever, as long as the
utility being front-ended provided the interagtion grammar via a
standard interaction mechanism to the (G)UI.
Obviously, there needs to be the concept of "environment" and "get
default from environment", with the environent maintained by the
utility being fronted, and not the front end itself.
For instance, ID probably wants to be defaulted to 'NEXT_UNUSED_ID'
from the utility environment, etc..
It's a pretty obvious dialog interaction model, if you think about
it, and it prevents spreading the maintenance hassle over a potentially
large number of programs, which may or may not be correctly synced.
For a good start on the uadmin utility:
makefile:
==========================================================================
LEX= lex -l
YACC= yacc
CFLAGS= -DYYDEBUG=1
YSRC= uadmin.y
LSRC= uadmin.l
P1= uadmin
P1SRCS= y.tab.c lex.yy.c defaults.c users.c groups.c
P1OBJS= $(P1SRCS:.c=.o)
all: $(P1)
$(P1): $(P1OBJS)
$(CC) -o $@ $> -lln -ly
y.tab.c y.tab.h: $(YSRC)
$(YACC) -d $>
lex.yy.c: $(LSRC)
$(LEX) $>
clean:
@rm -f $(P1) $(P1OBJS)
clobber: clean
@rm -f y.tab.c lex.yy.c
==========================================================================
uadmin.l:
==========================================================================
%{
#include "y.tab.h"
#include <math.h>
#include <string.h>
extern double vbltable[ 26];
%}
%%
show { return SHOW; }
nextuid { return NEXTUID; }
group { return GROUP; }
user { return USER; }
default { return DEFAULT; }
verbose { return VERBOSE; }
[A-Za-z][A-Za-z0-9]* { yylval.sval = strdup(yytext); return NAME; }
[0-9]* { yylval.ival = atoi(yytext); return NUM; }
([0-9]+|([0-9]*\.[0.9]+)([eE][-+]?[0-9]+)?) {
yylval.dval = atof(yytext); return NUMBER;
}
[ \t] ; /* ignore whitespace*/
[a-z] { yylval.vblno = yytext[0] - 'a'; return XNAME; }
"$" { return 0; /* end of input*/ }
\n |
. { return yytext[ 0]; }
%%
==========================================================================
uadmin.y
==========================================================================
%{
/*
* This file is machine generated.
*/
%}
%union {
char *sval;
int ival;
}
%token <dval> NUMBER
%token <ival> NUM
%type <ival> vopt
%token SHOW
%token NEXTUID
%token GROUP
%token USER
%token DEFAULT
%token VERBOSE
%token <sval> NAME
%left '-' '+'
%left '*' '/'
%nonassoc UMINUS
%type <dval> expression
%%
session:
prompt_statement
| session prompt_statement
;
prompt_statement:
statement '\n' { prompt(); }
;
statement: /* nothing*/
| show_expr
/* | expression { printf("= %g\n", $1); }*/
;
show_expr:
SHOW NEXTUID { nextuid_show(); }
| SHOW USER NAME vopt { user_show_name( $3, $4); }
| SHOW USER NUM vopt { user_show_num( $3, $4); }
| SHOW USER '*' vopt { user_show_all( $4); }
| SHOW GROUP NAME vopt { group_show_name( $3, $4); }
| SHOW GROUP NUM vopt { group_show_num( $3, $4); }
| SHOW GROUP '*' vopt { group_show_all( $4); }
| SHOW DEFAULT NAME { default_show_name( $3); }
| SHOW DEFAULT '*' { default_show_all(); }
;
vopt:
/* empty*/ { $$ = 0; }
| VERBOSE { $$ = 1; }
expression: expression '+' expression { $$ = $1 + $3; } |
expression '-' expression { $$ = $1 - $3; } |
expression '*' expression { $$ = $1 * $3; } |
expression '/' expression
{ if( $3 == 0.0)
yyerror("divide by zero");
else
$$ = $1 / $3;
}
| '-' expression %prec UMINUS { $$ = -$2; }
| '(' expression ')' { $$ = $2; }
| NUMBER
;
%%
void
yyerror(s)
const char *s;
{
printf("%s\n", s);
}
void
prompt(void)
{
printf( "UADMIN> ");
}
int main( int ac, char **av)
{
prompt();
while( yyparse()) {
/* syntax error*/
continue;
}
printf( "\n");
return 0;
}
==========================================================================
defaults.c
==========================================================================
/*
* defaults.c
*
* Routines to display and modify the program defaults
*/
void
default_show_name( char *str)
{
printf( "default by name '%s'\n", str);
}
void
default_show_all( void)
{
printf( "all defaults\n");
}
void
nextuid_show( void)
{
printf( "the next uid is ???\n");
}
/*
* EOF -- This file has not been truncated
*/
==========================================================================
groups.c
==========================================================================
/*
* groups.c
*
* Routines to display and modify the group database
*/
void
group_show_name( char *str, int verbose)
{
printf( "group by name '%s' %s\n", str, verbose ? "verbose" : "terse");
free( str); /* was strdup'ed by lex*/
}
void
group_show_num( int num, int verbose)
{
printf( "group by num '%d' %s\n", num, verbose ? "verbose" : "terse");
}
void
group_show_all( int verbose)
{
printf( "all groups %s\n", verbose ? "verbose" : "terse");
}
/*
* EOF -- This file has not been truncated
*/
==========================================================================
users.c
==========================================================================
/*
* users.c
*
* Routines to display and modify the user database
*/
#include <sys/types.h>
#include <sys/param.h> /* NGROUPS*/
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
char *
expire_time( time_t *timep)
{
return( "NEVER");
}
char *
groupname( int gid)
{
struct group *groupp;
if( ( groupp = getgrgid( gid)) == NULL)
return( "<NO NAME: getgrgid error");
else return( groupp->gr_name);
}
void
user_show( struct passwd *userp, int verbose)
{
int groups[ NGROUPS ];
int ngroups = NGROUPS;
int i;
printf( "NAME '%s'\n", userp->pw_gecos);
printf( "USER %s\n", userp->pw_name);
printf( "ID %d\n", userp->pw_uid);
if( verbose && userp->pw_passwd[ 0] != '*') {
printf( "PASSWORD '%s'\n", userp->pw_passwd);
}
printf( "GROUP %d (%s)\n", userp->pw_gid,
groupname( userp->pw_gid));
if( verbose &&
getgrouplist( userp->pw_name, userp->pw_gid, groups, &ngroups) != -1) {
for( i = 0; i < ngroups; i++) {
if( groups[ i] == userp->pw_gid)
continue; /* skip default*/
printf( "+GROUP %d (%s)\n",
groups[ i], groupname( groups[ i]));
}
}
printf( "HOME %s\n", userp->pw_dir);
printf( "SHELL %s\n", userp->pw_shell);
printf( "EXPIRE PASSWORD %s\n", expire_time(&userp->pw_change));
printf( "EXPIRE ACCOUNT %s\n", expire_time(&userp->pw_expire));
}
void
user_show_name( char *str, int verbose)
{
struct passwd *userp;
printf( "user by name '%s' %s\n", str, verbose ? "verbose" : "terse");
if( ( userp = getpwnam( str)) == NULL) {
printf( "No such user '%s'\n", str);
goto error;
}
user_show( userp, verbose);
error:
free( str); /* was strdup'ed by lex*/
}
void
user_show_num( int num, int verbose)
{
struct passwd *userp;
printf( "user by num '%d' %s\n", num, verbose ? "verbose" : "terse");
if( ( userp = getpwuid( num)) == NULL) {
printf( "No such user '%d'\n", num);
goto error;
}
user_show( userp, verbose);
error:
return;
}
void
user_show_all( int verbose)
{
printf( "all users %s\n", verbose ? "verbose" : "terse");
}
/*
* EOF -- This file has not been truncated
*/
==========================================================================
Terry Lambert
terry@lambert.org
---
Any opinions in this posting are my own and not those of my present
or previous employers.
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199605272104.OAA08998>
