Create a password generation program which will generate passwords containing random ASCII characters from the following groups:

         lower-case letters:  a ──► z
         upper-case letters:  A ──► Z
                     digits:  0 ──► 9
 other printable characters:  !"#$%&'()*+,-./:;<=>?@[]^_{|}~ 
 (the above character list excludes white-space, backslash and grave) 



The generated password(s) must include   at least one   (of each of the four groups):

   lower-case letter, 
   upper-case letter,
   digit  (numeral),   and 
   one  "other"  character. 



The user must be able to specify the password length and the number of passwords to generate.

The passwords should be displayed or written to a file, one per line.

The randomness should be from a system source or library.

The program should implement a help option or button which should describe the program and options when invoked.

You may also allow the user to specify a seed value, and give the option of excluding visually similar characters.

For example:            Il1     O0     5S     2Z            where the characters are:

   capital eye, lowercase ell, the digit one
   capital oh, the digit zero 
   the digit five, capital ess
   the digit two, capital zee


	cpu	8086
	bits	16
	;;;	MS-DOS syscalls
gettim:	equ	2Ch	; Get system time
write:	equ	40h	; Write to file
exit:	equ	4Ch	; Exit with return code
	;;;	MS-DOS process data
arg:	equ	80h	; Command line argument (length + string)
	;;;	BIOS calls
conout:	equ	0Eh	; Write character to console
vstate:	equ	0Fh	; Get current video state
section	.text
	org	100h
	mov	ah,gettim	; Seed the RNG using the system time
	int	21h		; (in case of no argument)
	mov	[rnddat],cx
	mov	[rnddat+2],dx
	mov	si,arg		; See if we have any arguments
	lodsb
	test	al,al
	jnz	hasarg
	jmp	usage		; If not, print usage string
	;;;	Parse the command line arguments
hasarg:	xor	bx,bx		; We do, zero-terminate the string
	xchg	al,bl
	mov	[si+bx],al
	mov	di,count	; Place to start reading arguments
doarg:	lodsb			; Get argument byte
.chr:	test	al,al		; Zero?
	jnz 	.arg		; If not, there are arguments left
	jmp	check		; If so, we're done
.arg:	cmp	al,' '		; Space?
	je	doarg		; Then get next character
	cmp	al,'/'		; Option?
	je	opt
	dec	byte [nargs]	; Otherwise, it's an argument
	jnz	rdnum		; Read a number if we still need one
	jmp	usage		; Otherwise, incorrect arguments
rdnum:	xor	bp,bp		; Place to keep number
.chr:	mov	bl,al		; Keep the character in case not a digit
	sub	al,'0'		; Make into digit
	cmp	al,9		; Valid digit?
	ja	.done		; If not, done with number
	add	bp,bp		; Multiply accumulator by 10
	mov	dx,bp
	add	bp,bp
	add 	bp,bp
	add	bp,dx
	xor	ah,ah		; Then add the digit
	add	bp,ax
	lodsb			; Read next digit
	jmp	.chr		
.done:	mov	ax,bp		; Write the number into memory
	stosw
	mov	al,bl		; Restore the character (next argument)
	jmp	doarg.chr
opt:	lodsb			; Get option character
	or	al,32		; Make lowercase
	cmp	al,'e'		; E?
	je	.e
	cmp	al,'s'		; S?
	je	.s
	jmp	usage		; If not, invalid argument
.e:	inc	byte [excl]	; /E: turn on exclusion
	jmp	doarg
.s:	lodsb			; /S: should be followed by '='
	cmp	al,'='
	je	.s_ok
	jmp	usage		; If not, invalid argument
.s_ok:	xor	bp,bp		; DX:BP = RNG state
	xor	dx,dx
.hex:	lodsb			; Get (potential) hex digit
	mov	ah,al		; Keep it around
	call	hexdgt		; Parse hexadecimal digit
	jnc	.hdone		; If invalid, we're done
	shl	bp,1		; Make room for it in the state
	rcl	dx,1		; Because it's a 32-bit value, 
	shl	bp,1		; which is stored in two 16-bit registers,
	rcl	dx,1		; we have to shift and rotate the bits
	shl	bp,1		; one by one.
	rcl	dx,1
	shl	bp,1
	rcl	dx,1
	or	dl,al		; Finally, add in the hexadecimal digit
	jmp	.hex
.hdone:	mov	[rnddat],bp	; Store random seed
	mov	[rnddat+2],dx
	mov	al,ah		; Restore the next character
	jmp	doarg.chr
	;;;	Check the arguments
check:	dec	byte [nargs]	; Were all arguments used?
	jz	.argok
	jmp	usage
.argok:	mov	cx,[count]	; CX = count
	mov	bp,[length]	; BP = length
	test	cx,cx		; Sanity check (count must not be zero)
	mov	si,errmsg.cnt
	jnz	.cntok
	jmp	error
.cntok:	cmp	bp,4		; Length must be at least four 
	mov	si,errmsg.len
	jae	.len2
	jmp	error
.len2:	cmp	bp,255		; Length must be no more than 255
	mov	si,errmsg.lmax
	jbe	mkmask
	jmp	error
	;;;	Make a bitmask in which BP fits (for generating random
	;;;	numbers in the right range)
mkmask:	mov	cx,bp
	mov	bx,cx
.loop:	shr	cx,1
	or	bx,cx
	test	cx,cx
	jnz	.loop
	stc
	rcl 	bx,1
	mov	[lmask],bl	; Password <= 255 chars
	;;;	Generate a password
genpwd:	lea	cx,[bp-4]	; Get length minus four
	mov	di,buffer	; Write password into buffer
	mov	dl,[excl]	; Exclusion flag
	mov	bx,lc		; A lowercase letter,
	mov	ah,lc.len	
	call	rndchr
	mov	bx,uc		; An uppercase letter
	call	rndchr		; (same length of course)
	mov	bx,dgt		; A digit,
	mov	ah,dgt.len
	call	rndchr
	mov	bx,sym		; And a symbol
	mov	ah,sym.len 
	call 	rndchr
	test	cx,cx		; If CX=0, we need no extra characters
	jz	.done
	mov	bx,cr		; Otherwise, we need CX extra characters
	mov	ah,cr.len
.loop:	call	rndchr
	loop	.loop
.done:	mov	ax,0A0Dh	; End with a newline
	stosw
	;;;	Swap the first four characters with random characters
	;;;	from the password, so the four necessary characters won't
	;;;	necessarily be at the beginning.
swap:	mov	dx,bp 		; Get length
	mov	dh,[lmask]	; Load the mask
	mov	si,buffer	; Password buffer
	xor	bh,bh
	xor 	ah,ah		; Starting at zero
.loc:	call	rand		; Get random byte
	and	al,dh		; Mask off unused bits
	cmp	al,dl		; Result within password?
	jae	.loc		; If not, get another random number
	mov	bl,ah		; Load first character in CL
	mov	cl,[si+bx]
	mov	bl,al		; Load random character in CH
	mov	ch,[si+bx]
	mov	[si+bx],cl	; Write them back the other way around
	mov	bl,ah
	mov	[si+bx],ch
	inc	ah		; Next character
	cmp	ah,4		; Are we there yet?
	jb	.loc		; If not, do another character.
	;;;	Write password to standard output
	xor	bx,bx		; File handle 0 = stdout
	lea	cx,[bp+2]	; Length = password length + 2 (for newline)
	mov	dx,si		; Location of password
	mov	ah,write	; Write the password
	int	21h
	jnc	.ok		; Carry clear = success
	mov	si,errmsg.wrt	; Otherwise, print error message
	jmp	error
.ok:	dec	word [count] 	; Need any more passwords?
	jz	stop		; If not, stop
	jmp	genpwd		; Otherwise, make another password
	;;;	Quit with return code 0 (success)
stop:	mov	ax,exit<<8
	int	21h
	;;;	Generate a random character. BX=table, AH=length,
	;;;	DL=exclusion on/off
rndchr:	call	rand		; Random number
	and	al,7Fh		; from 0 to 127
	cmp	al,ah		; Within valid length?
	jae	rndchr		; If not, get new random number
	xlatb			; Otherwise, get character from table
	test	dl,dl		; Is exclusion on?
	jz	.out		; If not, just store it
	push	cx		; Otherwise, keep CX around,
	push	di		; and DI,
	mov	cx,ex.len	; See if character is in exclusion table
	mov	di,ex
	repne	scasb
	pop	di		; restore DI
	pop	cx		; Restore CX
	je	rndchr		; And if it was found, get another character
.out:	stosb			; Store in password buffer
	ret
	;;;	Random number generator using XABC algorithm
	;;;	Returns random byte in AL
rand:	push	cx
	push	dx
	mov	cx,[rnddat]	; CH=X CL=A
	mov	dx,[rnddat+2]	; DH=B DL=C
	inc	ch		; X++
	xor	cl,ch		; A ^= X
	xor	cl,dl		; A ^= C
	add	dh,cl		; B += A
	mov	al,dh		; C' = B 
	shr	al,1		; C' >>= 1
	xor	al,cl		; C' ^= A
	add	al,dl		; C' += C
	mov	dl,al		; C = C'
	mov	[rnddat],cx
	mov	[rnddat+2],dx
	pop	dx
	pop	cx
	ret
	;;;	If AL is a hexadecimal digit in ASCII, set AL to be its 
	;;;	value. Carry flag set if digit was valid.
hexdgt:	or	al,32		; Make letter lowercase (numbers unchanged)
	sub	al,'0'		; Subtract 0
	cmp	al,10		; If it is a valid digit now, we're done
	jc	.out
	sub	al,39		; Otherwise, correct for 
	cmp	al,17	
.out:	ret
	;;;	Write the help message, and stop with return code 2
usage:	mov	si,help
	;;;	Write an error message, directly to the console, bypassing
	;;;	DOS to ensure it does not end up in the redirected output.
	;;;	Then, stop with return code 2 (failure)
error:	mov	ah,vstate	; Retrieve current video state
	int	10h		; (this sets BH = current page)
.loop	lodsb			; Get string byte
	test	al,al		; Zero?
	jz	.out		; Then we have reached the end
	mov	ah,conout	; Otherwise, write the character
	int	10h
	jmp	.loop		; And get another one
.out:	mov	ax,exit<<8|2	; Stop with error code 2
	int	21h 
section	.data
nargs:	db	3		; We need two non-option arguments
excl:	db	0		; Exclusion is off by default
cr:	;;;	Password characters
lc:	db	'abcdefghijklmnopqrstuvwxyz'
.len:	equ	$-lc
uc:	db	'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
.len:	equ	$-uc
dgt:	db	'0123456789'
.len:	equ	$-dgt
sym:	db	'!"#$%&()*+,-./:;<=>?@[]^_{|}~',39 ; 39=single quote
.len:	equ 	$-sym
cr.len:	equ	$-cr
ex:	db	'Il1O05S2Z'	; Excluded characters
.len:	equ	$-ex
	;;;	Help message
help:	db	'PASSGEN [/S=seed] [/E] count length',13,10
	db	9,'generate <count> passwords of length <length>',13,10
	db	13,10
	db	9,'/S=seed: set RNG seed (hexadecimal number)',13,10
	db	9,'/E:      exclude visually similar characters',13,10
	db	0
errmsg:	;;;	Error messages
.len:	db	'Minimum password length is 4.',0
.lmax:	db	'Maximum password length is 255.',0
.cnt:	db	'At least one password must be generated.',0
.wrt:	db	'Write error.',0
section	.bss
count:	resw	1		; Amount of passwords to generate
length:	resw	1		; Length of passwords
lmask:	resb	1		; Mask for random number generation
rnddat:	resb	4		; RNG state
buffer:	resb	257		; Space to store password