Skip site navigation (1)Skip section navigation (2)
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>