1 /*
2 * One-time password generator
3 *
4 * Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/>
5 */
6
7 #define _GNU_SOURCE
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <pwd.h>
13 #include <time.h>
14 #include <sys/time.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <unistd.h>
18 #include <errno.h>
19 #include <termios.h>
20 #include <assert.h>
21 #include <termios.h>
22 #include <limits.h>
23 #include "otpw.h"
24
25
26 #define NL "\r\n" /* new line sequence in password list output */
27 #define FF "\f\n" /* form feed sequence in password list output */
28 #define MAX_PASSWORDS 1000 /* maximum length of password list */
29 #define MASTERKEY_CHECKBITS 4 /* error-detection bits in master key */
30
31 /* shell commands that provide high entropy output for RNG */
32 char *entropy_cmds[] = {
33 "head -c 20 /dev/urandom 2>&1",
34 "ls -lu /etc/. /tmp/. / /usr/. /bin/. /usr/bin/.",
35 "PATH=/usr/ucb:/bin:/usr/bin;ps lax",
36 "last | head -50",
37 "uptime;netstat -n;hostname;date;w",
38 "cd $HOME; cat .pgp/randseed.bin .ssh/random_seed .otpw 2>&1"
39 /* too slow: "PATH=/usr/bin/X11/;xwd -root -silent 2>&1||xwd -root 2>&1" */
40 };
41
42 /* Environment variable settings for entropy_cmds */
43 char *entropy_env[] = {
44 "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc:/usr/etc:/usr/ucb"
45 };
46
47 /* Minimum password entropy [bits] permitted by otpw-gen (option -e) */
48 int emin=30;
49
50 /* suffix added to temporary OTPW file */
51 char *tmpsuffix = ".tmp";
52
53 /*
54 * A list of common English four letter words. It has not been checked
55 * particularly well for being free of rude words or trademarks; users
56 * are meant to keep them secret anyway.
57 */
58
59 char word[2048][4] = {
60 "abel","able","ably","acer","aces","acet","ache","acid","acne","acre",
61 "acts","adam","adds","aden","afar","aged","ages","aide","aids","aims",
62 "airs","airy","ajar","akin","alan","alas","alec","ales","alex","alix",
63 "ally","alma","alps","also","alto","amen","ames","amid","amis","amos",
64 "amps","anal","andy","anew","ange","angy","anna","anne","ante","anti",
65 "ants","anus","anya","aoun","apes","apex","apse","arab","arch","arcs",
66 "ards","area","aria","arid","arms","army","arts","asda","asia","asks",
67 "atom","atop","audi","aung","aunt","aura","auto","avid","aviv","avon",
68 "away","awry","axed","axes","axis","axle","aziz","baba","babe","baby",
69 "bach","back","bade","bags","bail","bait","bake","baku","bald","bale",
70 "bali","ball","balm","band","bang","bank","bans","bare","bark","barn",
71 "barr","bars","bart","base","bash","bass","bath","bats","bays","bcci",
72 "bdda","bead","beak","beam","bean","bear","beat","beck","bede","beds",
73 "beef","been","beep","beer","bees","begs","bell","belt","bend","benn",
74 "bent","berg","bert","best","beta","beth","bets","bias","bids","biff",
75 "bike","bile","bill","bind","bins","bird","birk","birt","bite","bits",
76 "blah","blew","blip","blob","bloc","blot","blow","blue","blur","boar",
77 "boat","bodo","body","boer","bogs","boil","bold","bolt","bomb","bond",
78 "bone","bonn","bono","bony","book","boom","boon","boot","bore","borg",
79 "born","boro","boss","both","bout","bowe","bowl","bows","boyd","boys",
80 "brad","bran","bras","brat","bray","bred","brew","brim","brom","bros",
81 "brow","buck","buds","buff","bugs","bulb","bulk","bull","bump","bums",
82 "bunk","buns","buoy","burn","burr","burt","bury","bush","bust","busy",
83 "butt","buys","buzz","byre","byte","cabs","cafe","cage","cain","cake",
84 "calf","call","calm","came","camp","cane","cans","cape","caps","capt",
85 "cara","card","care","carl","caro","carp","carr","cars","cart","casa",
86 "case","cash","cask","cast","cats","cave","cdna","cegb","cell","cent",
87 "cert","chad","chan","chap","chas","chat","chef","chen","cher","chew",
88 "chic","chin","chip","chop","chub","chum","cite","city","clad","clan",
89 "claw","clay","cleo","clio","clip","club","clue","cnaa","cnut","coal",
90 "coat","coax","coca","code","cohn","coil","coin","coke","cola","cold",
91 "cole","coli","colt","coma","comb","come","comp","cone","cons","cook",
92 "cool","cope","cops","copy","cord","core","cork","corn","corp","cose",
93 "cost","cosy","cots","coun","coup","cove","cows","cpre","cpsu","cpus",
94 "crab","crag","crap","cray","creb","crew","crim","crop","crow","crux",
95 "cruz","csce","cuba","cube","cubs","cues","cuff","cult","cunt","cups",
96 "curb","curd","cure","curl","curt","cute","cuts","daak","dada","dads",
97 "daft","dahl","dais","dale","daly","dame","damn","damp","dams","dana",
98 "dane","dank","dare","dark","dart","dash","data","date","dave","davy",
99 "dawn","days","daze","dead","deaf","deal","dean","dear","debt","deck",
100 "deed","deep","deer","deft","defy","dell","demo","deng","dent","deny",
101 "dept","desk","dial","dice","dick","died","dies","diet","digs","dine",
102 "ding","dino","dint","dire","dirk","dirt","disc","dish","disk","dive",
103 "dock","dodd","does","dogs","dole","doll","dome","done","dons","doom",
104 "door","dope","dora","dose","doth","dots","doug","dour","dove","dowd",
105 "down","drab","drag","draw","drew","drip","drop","drug","drum","dual",
106 "duck","duct","duel","dues","duet","duff","duke","dull","duly","duma",
107 "dumb","dump","dune","dung","dunn","dusk","dust","duty","dyer","dyes",
108 "dyke","each","earl","earn","ears","ease","east","easy","eats","echo",
109 "ecsc","eddy","eden","edge","edgy","edie","edit","edna","edta","eels",
110 "efta","egan","eggs","egon","egos","eire","ella","else","emil","emit",
111 "emma","ends","enid","envy","epic","ercp","eric","erik","esau","esrc",
112 "esso","eton","euro","evan","even","ever","evil","ewen","ewes","exam",
113 "exit","exon","expo","eyed","eyes","eyre","ezra","face","fact","fade",
114 "fads","fags","fail","fair","fake","fall","fame","fand","fans","fare",
115 "farm","farr","fast","fate","fats","fawn","faye","fear","feat","feed",
116 "feel","fees","feet","fell","felt","fend","fenn","fens","fern","fete",
117 "feud","fiat","fife","figs","fiji","file","fill","film","find","fine",
118 "finn","fins","fire","firm","fish","fist","fits","five","flag","flak",
119 "flap","flat","flaw","flea","fled","flee","flew","flex","flip","flop",
120 "flow","floy","flue","flux","foal","foam","foci","foes","foil","fold",
121 "folk","fond","font","food","fool","foot","ford","fore","fork","form",
122 "fort","foul","four","fowl","fran","frau","fray","fred","free","fret",
123 "frog","from","ftse","fuel","fuji","full","fund","funk","furs","fury",
124 "fuse","fuss","fyfe","gael","gail","gain","gait","gala","gale","gall",
125 "game","gang","gaol","gaps","garb","gary","gash","gasp","gate","gatt",
126 "gaul","gave","gays","gaza","gaze","gcse","gear","gels","gems","gene",
127 "gens","gent","germ","gets","gift","gigs","gill","gilt","gina","girl",
128 "gist","give","glad","glee","glen","glow","glue","glum","goal","goat",
129 "gods","goes","goff","gogh","gold","golf","gone","good","gore","gory",
130 "gosh","gown","grab","graf","gram","gran","gray","greg","grew","grey",
131 "grid","grim","grin","grip","grit","grow","grub","guil","gulf","gull",
132 "gulp","gums","gunn","guns","guru","gust","guts","guys","gwen","hack",
133 "haig","hail","hair","hale","half","hall","halo","halt","hams","hand",
134 "hang","hank","hans","hard","hare","hari","harm","harp","hart","hash",
135 "hate","hath","hats","hatt","haul","have","hawk","haze","hazy","head",
136 "heal","heap","hear","heat","heck","heed","heel","heir","hela","held",
137 "hell","helm","help","hens","herb","herd","here","hero","herr","hers",
138 "hess","hibs","hick","hide","high","hike","hill","hilt","hind","hint",
139 "hips","hire","hiss","hits","hive","hiya","hmso","hoax","hogg","hold",
140 "hole","holt","holy","home","hong","hons","hood","hoof","hook","hoop",
141 "hope","hops","horn","hose","host","hour","hove","howe","howl","hrun",
142 "hues","huge","hugh","hugo","hulk","hull","hume","hump","hung","hunt",
143 "hurd","hurt","hush","huts","hyde","hype","iaea","iago","iain","ibid",
144 "iboa","iced","icon","idea","idle","idly","idol","igor","ills","inca",
145 "ince","inch","info","inns","insp","into","iona","ions","iowa","iran",
146 "iraq","iris","iron","isis","isle","itch","item","ivan","ives","ivor",
147 "jack","jade","jail","jake","jams","jane","jars","java","jaws","jazz",
148 "jean","jeep","jeff","jerk","jess","jest","jets","jett","jews","jill",
149 "jimi","joan","jobs","jock","joel","joey","john","join","joke","jolt",
150 "jose","josh","joys","juan","judd","jude","judi","judo","judy","jugs",
151 "july","jump","june","jung","junk","jury","just","kahn","kane","kant",
152 "karl","karr","kate","kath","katy","katz","kaye","keel","keen","keep",
153 "kemp","kent","kept","kerb","kerr","keys","khan","kick","kidd","kids",
154 "kiev","kiff","kill","kiln","kilo","kilt","kind","king","kirk","kiss",
155 "kite","kits","kiwi","knee","knew","knit","knob","knot","know","knox",
156 "koch","kohl","kong","kuhn","kurt","kyle","kyte","labs","lace","lack",
157 "lacy","lads","lady","laid","lain","lair","lais","lake","lama","lamb",
158 "lame","lamp","land","lane","lang","laos","laps","lard","lark","lass",
159 "last","late","lava","lawn","laws","lays","lazy","lead","leaf","leak",
160 "lean","leap","lear","leas","lech","lees","left","legs","lend","lens",
161 "lent","leon","less","lest","lets","levi","levy","leys","liam","liar",
162 "lice","lick","lids","lied","lien","lies","life","lift","like","lili",
163 "lily","lima","limb","lime","limp","lina","line","ling","link","lino",
164 "lion","lips","lira","lire","lisa","list","live","liza","load","loaf",
165 "loan","lobe","loch","lock","loco","loft","logo","logs","lois","lone",
166 "long","look","loom","loop","loos","loot","lord","lore","lori","lose",
167 "loss","lost","lots","loud","love","lowe","ltte","luce","luch","luck",
168 "lucy","ludo","luis","luke","lull","lump","lung","lure","lush","lust",
169 "lute","lyle","lyon","mabs","mace","mach","mack","made","maid","mail",
170 "main","mait","make","mala","male","mali","mall","malt","mama","mane",
171 "mann","mans","manx","many","maps","marc","mare","mark","marr","mars",
172 "marx","mary","mash","mask","mass","mast","mate","mats","matt","maud",
173 "mayo","maze","mead","meal","mean","meat","meek","meet","mega","melt",
174 "memo","mend","mens","menu","mere","mesh","mess","mice","mick","midi",
175 "mike","mild","mile","milk","mill","mime","mind","mine","minh","mini",
176 "mink","mins","mint","mips","mira","mire","miss","mist","mite","moan",
177 "moat","mobs","moby","mock","mode","modi","mold","mole","mona","monk",
178 "mono","mont","mood","moon","moor","moot","more","mori","moss","most",
179 "moth","mott","move","mrna","much","muck","mugs","muir","mule","mull",
180 "mums","muon","muse","must","mute","myra","nacl","naff","nail","name",
181 "nana","nape","nasa","nash","nato","nave","navy","neal","near","neat",
182 "neck","need","neil","nell","neon","nero","ness","nest","nets","news",
183 "next","nice","nick","niki","nile","nina","nine","niro","noah","node",
184 "nods","noel","noir","nome","nona","none","noon","nope","nora","norm",
185 "nose","note","noun","nova","nowt","nude","null","numb","nunn","nuns",
186 "nupe","nuts","oaks","oars","oath","oats","oban","obey","oboe","odds",
187 "oecd","offa","ohio","ohms","oils","oily","okay","olds","olga","oman",
188 "omar","omen","omit","once","ones","only","onto","onus","oops","opal",
189 "opcs","opec","open","oral","orcs","ores","orgy","oslo","otto","ould",
190 "ours","oust","outs","oval","oven","over","owed","owen","owes","owls",
191 "owns","oxen","pace","pack","pact","pads","page","pahl","paid","pain",
192 "pair","pale","pall","palm","pals","pane","pang","pans","papa","para",
193 "park","parr","part","pass","past","pate","path","paul","pave","pawn",
194 "paws","pays","peak","pear","peas","peat","peck","peel","peer","pegs",
195 "peng","penh","penn","pens","pepe","perm","pers","pert","peru","pest",
196 "pete","pets","pews","phew","phil","pick","pied","pier","pies","pigs",
197 "pike","pile","pill","pine","ping","pink","pins","pint","pipe","pips",
198 "pisa","piss","pits","pitt","pity","pius","plan","play","plea","plot",
199 "ploy","plug","plum","plus","pods","poem","poet","poke","pole","poll",
200 "polo","poly","pomp","pond","pons","pont","pony","pooh","pool","poor",
201 "pope","pops","pore","pork","porn","port","pose","posh","posi","post",
202 "pots","pour","pram","prat","pray","prep","pres","prey","prim","prix",
203 "prof","prop","pros","prow","pubs","puff","pugh","pull","pulp","pump",
204 "punk","punt","puny","pups","pure","push","puts","putt","quay","quid",
205 "quit","quiz","race","rack","racy","raft","rage","rags","raid","rail",
206 "rain","rake","ramp","rams","rang","rank","rape","rapt","rare","rash",
207 "rate","rats","rave","rays","rbge","rdbi","read","real","reap","rear",
208 "reds","reed","reef","reel","rees","refs","reid","rein","rely","rene",
209 "rent","reps","rest","retd","revd","revs","reza","rhee","riba","ribs",
210 "rica","rice","rich","rick","rico","ride","rife","rift","riga","rigs",
211 "rind","ring","rink","riot","ripe","risc","rise","risk","rita","rite",
212 "ritz","riva","rnli","road","roam","roar","robb","robe","rock","rode",
213 "rods","role","rolf","roll","roma","rome","roof","rook","room","root",
214 "rope","rory","rosa","rose","ross","rosy","rota","roth","rout","rowe",
215 "rows","rubs","ruby","ruck","rudd","rude","rugs","ruin","rule","rump",
216 "rune","rung","runs","ruse","rush","russ","rust","ruth","ryan","sack",
217 "safe","saga","sage","said","sail","sake","sale","salt","same","sand",
218 "sane","sang","sank","sans","sara","sash","saul","save","saws","says",
219 "sbus","scan","scar","scot","scsi","scum","seal","seam","sean","seas",
220 "seat","secs","sect","seed","seek","seem","seen","seep","sees","sega",
221 "sejm","self","sell","sema","semi","send","sent","sept","sera","serb",
222 "serc","seth","sets","seve","sewn","sexy","shae","shah","shai","sham",
223 "shaw","shed","shia","shih","shin","ship","shoe","shop","shot","show",
224 "shut","sick","side","sigh","sign","sikh","silk","sill","silt","sims",
225 "sine","sing","sink","sins","site","sits","size","skin","skip","skis",
226 "skye","slab","slag","slam","slap","slid","slim","slip","slit","slot",
227 "slow","slug","slum","slur","slut","smog","smug","snag","snap","snip",
228 "snob","snow","snub","snug","soak","soap","soar","sobs","sock","soda",
229 "sofa","soft","soho","soil","sold","sole","solo","some","song","sons",
230 "sony","soon","soot","sore","sort","soul","soup","sour","sown","sows",
231 "soya","span","spar","spat","spec","sped","spin","spit","spot","spun",
232 "spur","ssap","stab","stag","stan","star","stay","stem","step","stew",
233 "stir","stok","stop","stow","stub","stud","subs","such","suck","sued",
234 "suez","suit","sums","sung","sunk","suns","supt","sure","surf","suzi",
235 "suzy","swam","swan","swap","sway","swig","swim","tabs","tack","tact",
236 "taff","tags","tail","tait","take","tale","talk","tall","tame","tang",
237 "tank","tape","taps","tara","tart","task","tate","taut","taxi","teak",
238 "teal","team","tear","teas","tech","tecs","teen","tees","tell","tend",
239 "tens","tent","term","tess","test","text","thai","than","that","thaw",
240 "thee","them","then","theo","they","thin","this","thou","thud","thug",
241 "thus","tick","tide","tidy","tied","tier","ties","tile","till","tilt",
242 "time","tina","tins","tiny","tips","tire","tito","toad","toby","todd",
243 "toes","togo","toil","told","toll","tomb","tome","tone","toni","tons",
244 "tony","took","tool","tops","tore","torn","tort","tory","toss","tour",
245 "town","toys","tram","trap","tray","tree","trek","trim","trio","trip",
246 "trna","trod","trot","troy","true","tsar","tube","tubs","tuck","tuna",
247 "tune","tung","turf","turk","turn","tvei","twig","twin","twit","twos",
248 "tyne","type","tyre","ucta","uefa","ugly","uist","undo","unit","unix",
249 "unto","upon","urea","urge","urgh","used","user","uses","ussr","utah",
250 "vain","vale","vane","vans","vary","vase","vass","vast","vats","veal",
251 "veil","vein","vent","vera","verb","vern","very","vest","veto","vets",
252 "vial","vibe","vice","view","vile","vine","visa","vita","vivo","void",
253 "vole","volt","vote","vous","vows","wabi","wacc","wade","wage","wail",
254 "wait","wake","walk","wall","walt","wand","wang","want","ward","ware",
255 "warm","warn","warp","wars","wary","wash","wasp","watt","wave","wavy",
256 "ways","weak","wear","webb","webs","weed","week","weep","weir","well",
257 "went","wept","were","west","what","when","whig","whim","whip","whit",
258 "whoa","whom","wick","wide","wife","wigs","wild","will","wily","wind",
259 "wine","wing","wink","wins","wipe","wire","wiry","wise","wish","with",
260 "wits","woes","woke","wolf","womb","wont","wood","wool","word","wore",
261 "work","worm","worn","wove","wrap","wren","writ","wyre","yale","yang",
262 "yard","yarn","yawn","yeah","year","yell","yoga","yoke","yolk","york",
263 "your","yous","yuan","yuri","yves","zach","zack","zapt","zeal","zero",
264 "zest","zeta","zeus","zinc","zone","zoom","zoos","zzap"
265 };
266
267 int debug = 0;
268
269
270 /* add the output and time of a shell command to message digest */
271
gurgle(md_state * mdp,char * command)272 void gurgle(md_state *mdp, char *command)
273 {
274 FILE *f;
275 char buf[128];
276 long len = 0, l;
277 struct timeval t;
278
279 f = popen(command, "r");
280 gettimeofday(&t, NULL);
281 md_add(mdp, &t, sizeof(t));
282 if (!f) {
283 fprintf(stderr, "External entropy source command '%s'\n"
284 "(one of several) failed.\n", command);
285 return;
286 }
287 while (!feof(f) && !ferror(f)) {
288 len += l = fread(buf, 1, sizeof(buf), f);
289 md_add(mdp, buf, l);
290 }
291 if (len == 0)
292 fprintf(stderr, "External entropy source command '%s'\n"
293 "returned no output.\n", command);
294 else
295 if (debug)
296 fprintf(stderr, "'%s' added %ld bytes.\n", command, len);
297 pclose(f);
298 gettimeofday(&t, NULL);
299 md_add(mdp, &t, sizeof(t));
300 }
301
302
303 /* A random bit generator. Hashes together various sources of entropy
304 * to provide a 16 byte high quality random seed */
305
306 /* Determine the initial start state of the random bit generator */
307
rbg_seed(unsigned char * r)308 void rbg_seed(unsigned char *r)
309 {
310 unsigned i;
311 md_state md;
312 struct {
313 clock_t clk;
314 pid_t pid;
315 uid_t uid;
316 pid_t ppid;
317 } entropy;
318
319 md_init(&md);
320
321 /* get entropy via some shell commands */
322 for (i = 0; i < sizeof(entropy_env)/sizeof(char*); i++)
323 putenv(entropy_env[i]);
324 for (i = 0; i < sizeof(entropy_cmds)/sizeof(char*); i++)
325 gurgle(&md, entropy_cmds[i]);
326
327 /* other minor sources of entropy */
328 entropy.clk = clock();
329 entropy.uid = getuid();
330 entropy.pid = getpid();
331 entropy.ppid = getppid();
332
333 md_add(&md, &entropy, sizeof(entropy));
334
335 md_close(&md, r);
336 }
337
338
339 /* Determine the next random bit generator state by hashing the
340 * previous state along with the the time and some magic string */
341
rbg_iter(unsigned char * r)342 void rbg_iter(unsigned char *r)
343 {
344 md_state md;
345 struct timeval t;
346
347 md_init(&md);
348 gettimeofday(&t, NULL);
349 md_add(&md, &t, sizeof(t));
350 md_add(&md, r, MD_LEN);
351 md_add(&md, "AutomaGic", 9); /* feel free to change this as a site key */
352 md_close(&md, r);
353 }
354
355
356 /* Generate a random byte string s with len bytes from a
357 * seed string seed with length slen */
358
random_string(const void * seed,size_t slen,void * s,size_t len)359 void random_string(const void *seed, size_t slen, void *s, size_t len)
360 {
361 md_state md;
362 unsigned char r[MD_LEN];
363 size_t i;
364 char j;
365
366 assert(len <= 0xffffffff);
367 md_init(&md);
368 md_add(&md, seed, slen);
369 md_close(&md, r);
370 for (i = 0; i < len; i++) {
371 *((unsigned char *)s++) = r[0];
372 md_init(&md);
373 j = i >> 24; md_add(&md, &j, 1);
374 j = i >> 16; md_add(&md, &j, 1);
375 j = i >> 8; md_add(&md, &j, 1);
376 j = i; md_add(&md, &j, 1);
377 md_add(&md, r, MD_LEN); /* this lines does not add entropy */
378 md_add(&md, seed, slen); /* that is not already added here! */
379 md_close(&md, r);
380 }
381 }
382
383
384 /*
385 * Transform the first 6*chars bits of the binary string v into a chars
386 * character long string s. The encoding is a modification of the MIME
387 * base64 encoding where characters with easily confused glyphs are
388 * avoided (0 vs O, 1 vs. l vs. I).
389 */
390
conv_base64(char * s,const unsigned char * v,int chars)391 void conv_base64(char *s, const unsigned char *v, int chars)
392 {
393 static const char tab[] =
394 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijk%mnopqrstuvwxyz"
395 ":=23456789+/";
396 int i, j;
397
398 for (i = 0; i < chars; i++) {
399 j = (i / 4) * 3;
400 switch (i % 4) {
401 case 0: *s++ = tab[ v[j] >>2]; break;
402 case 1: *s++ = tab[((v[j] <<4) & 0x30) | (v[j+1]>>4)]; break;
403 case 2: *s++ = tab[((v[j+1]<<2) & 0x3c) | (v[j+2]>>6)]; break;
404 case 3: *s++ = tab[ v[j+2] & 0x3f]; break;
405 }
406 }
407 *s++ = '\0';
408 }
409
410
411 /*
412 * Transform the first 5*chars bits of the binary string v into a chars
413 * character long string s. The encoding uses only lowercase letters
414 * and digit, in order to make it easy to communicate by voice (e.g.,
415 * using the NATO alphabet).
416 */
417
conv_base32(char * s,const unsigned char * v,int chars)418 void conv_base32(char *s, const unsigned char *v, int chars)
419 {
420 static const char tab[] =
421 "abcdefghijkmnpqrstuvwxyz23456789";
422 int i, j = 0, k = 0;
423
424 for (i = 0; i < chars; i++) {
425 if (k < 5) {
426 j = j << 8 | *(v++);
427 k += 8;
428 }
429 *s++ = tab[(j >> (k-5)) & 31];
430 k -= 5;
431 }
432 *s++ = '\0';
433 }
434
435
436 /*
437 * Normalize a password by removing whitespace etc. and converting
438 * l1| -> I, 0 -> O, \ -> /, just like otpw_verify() does.
439 */
pwnorm(char * password)440 void pwnorm(char *password) {
441 char *src, *dst;
442
443 src = dst = password;
444 while (1) {
445 if (*src == 'l' || *src == '1' || *src == '|')
446 *dst++ = 'I';
447 else if (*src == '0')
448 *dst++ = 'O';
449 else if (*src == '\\')
450 *dst++ = '/';
451 else if ((*src >= 'A' && *src <= 'Z') ||
452 (*src >= 'a' && *src <= 'z') ||
453 (*src >= '2' && *src <= '9') ||
454 *src == ':' ||
455 *src == '%' ||
456 *src == '=' ||
457 *src == '+' ||
458 *src == '/')
459 *dst++ = *src;
460 else if (*src == '\0') {
461 *dst++ = *src;
462 return;
463 }
464 src++;
465 }
466 }
467
468
469 #define PW_BASE64 0
470 #define PW_WORD4 1
471 #define PW_BASE32 2
472
473 /*
474 * Convert a random bit sequence into a printable password
475 *
476 * Input: vr random bit string
477 * vlen length of vr in bytes
478 * type 0: modified base-64 encoding
479 * 1: sequence of 4-letter words
480 * 2: base-32 encoding (lowercase plus digits)
481 * entropy requested minimum entropy of password
482 * buf buffer for returning zero-terminated output password
483 * buflen length of buffer in bytes
484 *
485 * Returns negative value if provided combination of vlen, type,
486 * ent and buflen are not adequate, otherwise return length of
487 * generated password (excluding terminating '\0').
488 *
489 * If buf == NULL, return value depends on buflen:
490 *
491 * 0: length of password that would have been generated
492 * 1: number of its non-space password characters
493 * 2: actually used entropy if buflen == 2
494 * 3: maximum entropy that can be specified for given vlen
495 */
make_passwd(const void * vr,int vlen,int type,int entropy,char * buf,int buflen)496 int make_passwd(const void *vr, int vlen, int type, int entropy,
497 char *buf, int buflen)
498 {
499 int pwchars; /* number of characters in password */
500 int pwlen; /* length of password, including whitespace */
501 int emax;
502 int i, j, k;
503 const unsigned char *v = vr;
504
505 /* calculate length of output and actually used entropy */
506 switch (type) {
507 case PW_BASE32:
508 pwchars = (entropy + 4) / 5;
509 entropy = pwchars * 5;
510 pwlen = pwchars + (pwchars > 5 ? (pwchars - 1) / 4 : 0);
511 emax = ((vlen * 8) / 5) * 5;
512 break;
513 case PW_BASE64:
514 pwchars = (entropy + 5) / 6;
515 entropy = pwchars * 6;
516 pwlen = pwchars + (pwchars > 5 ? (pwchars - 1) / 4 : 0);
517 emax = ((vlen * 8) / 6) * 6;
518 break;
519 case PW_WORD4:
520 pwchars = 4 * ((entropy + 10) / 11);
521 entropy = 11 * ((entropy + 10) / 11);
522 pwlen = pwchars + pwchars / 4 - (pwchars > 0);
523 emax = ((vlen * 8) / 11) * 11;
524 break;
525 default:
526 return -1;
527 }
528
529 if (!buf) {
530 switch (buflen) {
531 case 0: return pwlen; /* including spaces */
532 case 1: return pwchars; /* excluding spaces */
533 case 2: return entropy;
534 case 3: return emax;
535 default: return -2;
536 }
537 }
538 if (entropy > vlen * 8)
539 return -3;
540 if (pwlen >= buflen)
541 return -4;
542
543 switch (type) {
544 case PW_BASE32:
545 case PW_BASE64:
546 if (type == PW_BASE32)
547 conv_base32(buf, v, pwchars);
548 else
549 conv_base64(buf, v, pwchars);
550 /* add spaces every 3-4 chars for readability (Bresenham's algorithm) */
551 i = pwchars - 1;
552 j = pwlen - 1;
553 k = (pwlen - pwchars) / 2;
554 while (i >= 0 && j >= 0) {
555 buf[j--] = buf[i--];
556 if ((k += pwlen - pwchars + 1) >= pwchars && j > 0) {
557 buf[j--] = ' ';
558 k -= pwchars;
559 }
560 }
561 buf[pwlen] = '\0';
562 break;
563 case PW_WORD4:
564 for (i = 0; i < pwchars/4; i++) {
565 k = 0;
566 for (j = i * 11; j < (i+1) * 11; j++)
567 k = (k << 1) | ((v[j / 8] >> (j % 8)) & 1);
568 memcpy(buf + i * 5, word[k], 4);
569 buf[i * 5 + 4] = ' ';
570 }
571 buf[i * 5 - 1] = '\0';
572 break;
573 default:
574 return -1;
575 }
576
577 assert((int) strlen(buf) == pwlen);
578
579 return pwlen;
580 }
581
582
main(int argc,char ** argv)583 int main(int argc, char **argv)
584 {
585 unsigned char r[MD_LEN], h[MD_LEN];
586 md_state md;
587 int i, j, k, l;
588 struct otpw_pwdbuf *user = NULL, *pseudouser = NULL;
589 FILE *f;
590 char timestr[81], hostname[81], challenge[81];
591 char password1[1024], password2[1024];
592 char *password;
593 char *masterkey, *normal_masterkey = NULL;
594 int pwlen, pwchars, mklen;
595 char header[LINE_MAX];
596 char *fnout = NULL;
597 char *fntmp;
598 struct termios term, term_old;
599 int stdin_is_tty = 0;
600 int width = 79, height = 60, pages = 1, rows;
601 int header_lines = 4, random_order = 1;
602 int entropy = 48, emax, type = PW_BASE64;
603 int key_entropy = 76, key_type = PW_BASE32;
604 int use_masterkey = 0, regenerate = 0, unlock = 0;
605 int cols;
606 time_t t;
607 char *hbuf, *rndbuf;
608 int rndbuflen;
609 int challen = 3; /* number of characters in challenge */
610 int hbuflen = challen + otpw_hlen + 1;
611 int help = 0;
612
613 assert(md_selftest() == 0);
614 assert(otpw_hlen * 6 < MD_LEN * 8);
615 assert(otpw_hlen >= 8);
616
617 /* read command line arguments */
618 for (i = 1; i < argc && !help; i++) {
619 if (argv[i][0] == '-')
620 for (j = 1; j > 0 && !help && argv[i][j] != 0; j++)
621 switch (argv[i][j]) {
622 case 'h':
623 if (++i >= argc) { help = 1; break; }
624 height = atoi(argv[i]);
625 j = -1;
626 break;
627 case 'w':
628 if (++i >= argc) { help = 1; break; };
629 width = atoi(argv[i]);
630 j = -1;
631 break;
632 case 's':
633 if (++i >= argc) { help = 1; break; }
634 pages = atoi(argv[i]);
635 if (pages < 1) {
636 fprintf(stderr, "Specify at least 1 page after -s!\n");
637 exit(1);
638 }
639 j = -1;
640 break;
641 case 'e':
642 if (++i >= argc || (entropy = atoi(argv[i])) < 1)
643 { help = 1; break; }
644 j = -1;
645 break;
646 case 'E':
647 if (++i >= argc || (key_entropy = atoi(argv[i])) < 1)
648 { help = 1; break; }
649 j = -1;
650 break;
651 case 'p':
652 if (strlen(argv[i]+j) == 2 &&
653 argv[i][j+1] >= '0' && argv[i][j+1] <= '1') {
654 /* just for backwards compatibility with version 1.3 */
655 type = argv[i][j+1] - '0';
656 j = -1;
657 break;
658 }
659 if (++i >= argc)
660 { help = 1; break; }
661 type = atoi(argv[i]);
662 if (make_passwd(NULL, 0, type, 0, NULL, 0) == -1)
663 { help = 1; break; }
664 j = -1;
665 break;
666 case 'P':
667 if (++i >= argc)
668 { help = 1; break; }
669 key_type = atoi(argv[i]);
670 if (make_passwd(NULL, 0, key_type, 0, NULL, 0) == -1)
671 { help = 1; break; }
672 j = -1;
673 break;
674 case 'f':
675 if (++i >= argc) { help = 1; break; }
676 fnout = argv[i];
677 j = -1;
678 break;
679 case 'd':
680 debug = 1;
681 break;
682 case 'n':
683 header_lines = 0;
684 break;
685 case 'o':
686 random_order = 0;
687 break;
688 case 'm':
689 use_masterkey = 1;
690 break;
691 case 'k':
692 regenerate = 1;
693 break;
694 case 'r':
695 rbg_seed(r);
696 rndbuflen = entropy / 8 + 16;
697 rndbuf = malloc(rndbuflen);
698 pwlen = make_passwd(rndbuf, rndbuflen,
699 type, entropy, NULL, 0); /* pwlen */
700 assert(pwlen >= 0);
701 password = malloc(pwlen+1);
702 if (!rndbuf || !password) {
703 fprintf(stderr, "Memory allocation error!\n");
704 exit(1);
705 }
706 random_string(r, MD_LEN, rndbuf, rndbuflen);
707 assert(make_passwd(rndbuf, rndbuflen, type, entropy, NULL, 3)
708 >= entropy); /* emax */
709 l = make_passwd(rndbuf, rndbuflen, type, entropy,
710 password, pwlen+1);
711 assert(l >= 0);
712 printf("%s\n", password);
713 rbg_iter(r); rbg_iter(r); /* memory scrubbing */
714 memset(password, 0xaa, pwlen); /* memory scrubbing */
715 exit(0);
716 case 'l':
717 unlock = 1;
718 break;
719 default:
720 help = 1;
721 }
722 else {
723 help = 1;
724 }
725 }
726
727 if (fnout) {
728 /* if an output file was specified, drop privileges */
729 if (getuid() != geteuid()) {
730 if (setresuid(-1, getuid(), getuid()) || getuid() != geteuid()) {
731 fprintf(stderr, "Dropping setuid privileges failed!\n");
732 exit(1);
733 }
734 }
735 if (getgid() != getegid()) {
736 if (setresgid(-1, getgid(), getgid()) || getgid() != getegid()) {
737 fprintf(stderr, "Dropping setgid privileges failed!\n");
738 exit(1);
739 }
740 }
741 } else {
742 /* construct one-time password file path from relevant passwd entries */
743 otpw_getpwuid(getuid(), &user);
744 if (!user) {
745 fprintf(stderr, "Can't access your passwd database entry!\n");
746 exit(1);
747 }
748 if (getuid() != geteuid()) {
749 /* we are setuid pseudouser */
750 otpw_getpwuid(geteuid(), &pseudouser);
751 if (!pseudouser) {
752 fprintf(stderr, "Can't access setuid pseudouser passwd entry!\n");
753 exit(1);
754 }
755 fnout = (char *) malloc(strlen(pseudouser->pwd.pw_dir) + 1 +
756 strlen(user->pwd.pw_name) + 1);
757 if (!fnout) abort();
758 strcpy(fnout, pseudouser->pwd.pw_dir);
759 strcat(fnout, "/");
760 strcat(fnout, user->pwd.pw_name);
761 } else {
762 /* we are a normal user process */
763 fnout = (char *) malloc(strlen(user->pwd.pw_dir) + 1 +
764 strlen(otpw_file) + 1);
765 if (!fnout) abort();
766 strcpy(fnout, user->pwd.pw_dir);
767 strcat(fnout, "/");
768 strcat(fnout, otpw_file);
769 }
770 }
771
772 if (help) {
773 /* Print brief usage instructions, then abort */
774 fprintf(stderr, "One-Time Password Generator v 1.5 -- Markus Kuhn\n\n");
775 fprintf(stderr, "%s [options]\n\n", argv[0]);
776 fprintf
777 (stderr, "Options: (default or current value in parenthesis)\n\n"
778 " -h <int>\tnumber of output lines (60)\n"
779 " -w <int>\tmax width of output lines (79)\n"
780 " -s <int>\tnumber of output pages (1)\n"
781 " -e <int>\tminimum entropy of each one-time password [bits]\n"
782 "\t\t(low security: <30, default: 48, high security: >60)\n"
783 " -p 0\t\tpasswords from modified base64 encoding (default)\n"
784 " -p 1\t\tpasswords from English 4-letter words\n"
785 " -p 2\t\tpasswords use only lowercase letters and digits\n"
786 " -f <filename>\toutput hash file (%s)\n",
787 fnout);
788 fprintf
789 (stderr,
790 " -n\t\tdo not add header and footer lines to output\n"
791 " -o\t\tuse passwords in printed order (default: random order)\n"
792 " -m\t\tgenerate and display a master key for the password list\n"
793 " -E <int>\tminimum entropy of master key [bits] (76)\n"
794 " -P <int>\tencoding for master key (available values as for -p)\n"
795 " -k\t\task for a master key and then regenerate a password\n\t\tlist"
796 " from it (this won't change %s)\n", fnout);
797 fprintf
798 (stderr,
799 " -r\t\tsuggest a random password, then exit\n"
800 " -l\t\tremove lock file %s%s, then exit\n",
801 fnout, otpw_locksuffix);
802 fprintf
803 (stderr,
804 " -d\t\toutput debugging information\n"
805 "\nTypical uses:\n\n"
806 " otpw-gen | lpr\n\n"
807 " Generate password list and print it. The hash values for each "
808 "password\n will be stored in %s for use during login "
809 "verification.\n\n"
810 " otpw-gen -h 70 -s 2 | a2ps -1B -L 70 --borders no\n\n"
811 " Generate password list and print it with nicer formatting.\n\n",
812 fnout);
813 exit(1);
814 }
815
816 if (unlock) {
817 goto unlock;
818 }
819
820 /* check and process some parameters */
821 rows = height - header_lines;
822 if (rows <= 0) {
823 fprintf(stderr, "At least %d lines per page required (incl. header)!\n",
824 header_lines + 1);
825 exit(1);
826 }
827 if (width < 64 && header_lines) {
828 fprintf(stderr, "Specify not less than 64 character "
829 "wide lines!\n");
830 exit(1);
831 }
832 if (use_masterkey && regenerate) {
833 fprintf(stderr, "Options -m and -k are mutually exclusive!\n");
834 exit(1);
835 }
836
837 /* check whether entropy is ok */
838 if (entropy < emin) {
839 fprintf(stderr, "Entropy must be at least %d bits!\n", emin);
840 exit(1);
841 }
842 /* check whether entropy is ok */
843 if (key_entropy < 60) {
844 fprintf(stderr, "Masterkey entropy must be at least %d bits!\n", 60);
845 exit(1);
846 }
847
848 /* allocate buffers for password generation */
849 rndbuflen = (entropy > key_entropy ? entropy : key_entropy) / 8 + 16;
850 rndbuf = malloc(rndbuflen);
851 pwlen = make_passwd(NULL, rndbuflen, type, entropy, NULL, 0);
852 pwchars = make_passwd(NULL, rndbuflen, type, entropy, NULL, 1);
853 emax = make_passwd(NULL, rndbuflen, type, entropy, NULL, 3);
854 assert(pwlen > 0 && pwchars > 0 && emax > entropy);
855 password = malloc(pwlen + 1);
856 if (!rndbuf || !password) {
857 fprintf(stderr, "Memory allocation error!\n");
858 exit(1);
859 }
860
861 cols = (width + 2) / (challen + 1 + pwlen + 2);
862 if (cols < 1)
863 cols = 1;
864 if (pages * rows * cols > 1000) {
865 if (pages == 1)
866 rows = 1000 / cols;
867 }
868
869 if (debug)
870 fprintf(stderr, "pwlen=%d, pwchars=%d, emax=%d, cols=%d, rows=%d\n",
871 pwlen, pwchars, emax, cols, rows);
872
873 if (!regenerate) {
874 fprintf(stderr, "Generating random seed ...\n");
875 rbg_seed(r);
876
877 fprintf(stderr,
878 "\nIf your paper password list is stolen, the thief should not gain\n"
879 "access to your account with this information alone. Therefore, you\n"
880 "need to memorize and enter below a prefix password. You will have to\n"
881 "enter that each time directly before entering the one-time password\n"
882 "(on the same line).\n\n"
883 "When you log in, a %d-digit password number will be displayed. It\n"
884 "identifies the one-time password on your list that you have to append\n"
885 "to the prefix password. If another login to your account is in progress\n"
886 "at the same time, several password numbers may be shown and all\n"
887 "corresponding passwords have to be appended after the prefix\n"
888 "password. Best generate a new password list when you have used up half\n"
889 "of the old one.\n\n", challen);
890 }
891
892 /* disable echo if stdin is a terminal */
893 if (!tcgetattr(fileno(stdin), &term)) {
894 stdin_is_tty = 1;
895 term_old = term;
896 term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
897 if (tcsetattr(fileno(stdin), TCSAFLUSH, &term)) {
898 perror("tcsetattr");
899 exit(1);
900 }
901 }
902 if (!regenerate) {
903 /* check whether there is an old password list, to warn against
904 * accidental overwriting */
905 f = fopen(fnout, "r");
906 if (f) {
907 fclose(f);
908 fprintf(stderr, "Overwrite existing password list '%s' (Y/n)? ",
909 fnout);
910 if (!fgets(password1, sizeof(password1), stdin) ||
911 (password1[0] != '\n' && password1[0] != 'y' && password1[0] != 'Y')) {
912 if (stdin_is_tty)
913 tcsetattr(fileno(stdin), TCSANOW, &term_old);
914 fprintf(stderr, "\nAborted.\n");
915 exit(1);
916 }
917 fprintf(stderr, "\n\n");
918 }
919 }
920
921 /* ask for prefix password */
922 if (regenerate) {
923 fprintf(stderr, "Enter master key: ");
924 fgets(password1, sizeof(password1), stdin);
925 } else {
926 fprintf(stderr, "Enter new prefix password: ");
927 fgets(password1, sizeof(password1), stdin);
928 fprintf(stderr, "\nReenter prefix password: ");
929 fgets(password2, sizeof(password2), stdin);
930 }
931 if (stdin_is_tty)
932 tcsetattr(fileno(stdin), TCSANOW, &term_old);
933 if (regenerate) {
934 if (*password1)
935 password1[strlen(password1)-1] = 0; /* remove last character = LF */
936 normal_masterkey = password1;
937 pwnorm(normal_masterkey);
938 md_init(&md);
939 md_add(&md, normal_masterkey, strlen(normal_masterkey));
940 md_close(&md, h);
941 if (h[0] & (0xff - (1 << (8 - MASTERKEY_CHECKBITS)) + 1)) {
942 fprintf(stderr, "\nIncorrect master key (invalid checkbits)!\n");
943 exit(1);
944 }
945 } else {
946 if (strcmp(password1, password2)) {
947 fprintf(stderr, "\nThe two entered passwords were not identical!\n");
948 exit(1);
949 }
950 if (*password1)
951 password1[strlen(password1)-1] = 0; /* remove last character = LF */
952 }
953
954 if (regenerate)
955 fprintf(stderr, "\n\nRecreating one-time password list ...\n");
956 else
957 fprintf(stderr, "\n\nGenerating new one-time passwords ...\n\n");
958
959 if (header_lines) {
960 /* prepare header line that uniquely identifies this password list */
961 time(&t);
962 strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M", localtime(&t));
963 strcpy(hostname, "???");
964 gethostname(hostname, sizeof(hostname));
965 hostname[sizeof(hostname)-1] = 0;
966 snprintf(header, sizeof(header), regenerate ?
967 "OTPW list regenerated %s on %s" NL NL :
968 "OTPW list generated %s on %s" NL NL,
969 timestr, hostname);
970 }
971
972 /* allocate buffer for hash values */
973 hbuf = malloc(pages * rows * cols * hbuflen);
974 if (!hbuf) {
975 fprintf(stderr, "Memory allocation error!\n");
976 exit(1);
977 }
978
979 assert(MASTERKEY_CHECKBITS < 8);
980 if (use_masterkey) {
981 mklen = make_passwd(0, 0, key_type, key_entropy + MASTERKEY_CHECKBITS,
982 NULL, 0) + 1;
983 masterkey = malloc(mklen);
984 normal_masterkey = malloc(mklen);
985 if (!masterkey || !normal_masterkey) {
986 fprintf(stderr, "Memory allocation error!\n");
987 exit(1);
988 }
989 do {
990 /* generate new masterkey */
991 rbg_iter(r);
992 random_string(r, MD_LEN, rndbuf, rndbuflen);
993 make_passwd(rndbuf, rndbuflen, key_type,
994 key_entropy + MASTERKEY_CHECKBITS, masterkey, mklen);
995 strcpy(normal_masterkey, masterkey);
996 pwnorm(normal_masterkey);
997 md_init(&md);
998 md_add(&md, normal_masterkey, strlen(normal_masterkey));
999 md_close(&md, h);
1000 /* until its hash has the first MASTERKEY_CHECKBITS zero */
1001 } while (h[0] & (0xff - (1 << (8 - MASTERKEY_CHECKBITS)) + 1));
1002 fprintf(stderr, "Master key: %s\n"
1003 "(Option -k will recreate the same password list "
1004 "from this key.)\n\n", masterkey);
1005 }
1006
1007 for (l = 0; l < pages; l++) {
1008 if (header_lines)
1009 fputs(header, stdout);
1010 for (i = 0; i < rows; i++) {
1011 for (j = 0; j < cols; j++) {
1012 k = j * rows + i + l * rows * cols;
1013 snprintf(challenge, sizeof(challenge), "%03d", k);
1014 /* generate new password ... */
1015 if (use_masterkey || regenerate) {
1016 /* ... from masterkey and challenge string */
1017 md_init(&md);
1018 md_add(&md, normal_masterkey, strlen(normal_masterkey));
1019 md_add(&md, challenge, strlen(challenge));
1020 md_close(&md, h);
1021 random_string(h, MD_LEN, rndbuf, rndbuflen);
1022 } else {
1023 /* ... randomly */
1024 rbg_iter(r);
1025 random_string(r, MD_LEN, rndbuf, rndbuflen);
1026 }
1027 make_passwd(rndbuf, rndbuflen, type, entropy, password, pwlen + 1);
1028 /* output challenge */
1029 printf("%s %s", challenge, password);
1030 if (j < cols - 1)
1031 printf(" ");
1032 if (!regenerate) {
1033 /* hash password1 + pwnorm(password) and save result */
1034 md_init(&md);
1035 md_add(&md, password1, strlen(password1));
1036 pwnorm(password);
1037 md_add(&md, password, pwchars);
1038 md_close(&md, h);
1039 sprintf(hbuf + k * hbuflen, "%0*d", challen, k);
1040 conv_base64(hbuf + k*hbuflen + challen, h, otpw_hlen);
1041 }
1042 }
1043 if (i < rows - 1)
1044 printf(NL);
1045 }
1046 if (header_lines) {
1047 printf(NL NL "%*s", (cols*(challen + 1 + pwlen + 2) - 2)/2 + 50/2,
1048 "!!! REMEMBER: Enter the PREFIX PASSWORD first !!!");
1049 }
1050 if (l < pages - 1)
1051 printf(FF);
1052 else
1053 printf(NL);
1054 }
1055
1056 /* paranoia RAM scrubbing (note that we can't scrub stdout/stdin portably) */
1057 rbg_iter(r);
1058 rbg_iter(r);
1059 md_init(&md);
1060 md_add(&md,
1061 "Always clean up all memory that was in contact with secrets!!!!!!",
1062 65);
1063 md_close(&md, h);
1064 memset(password1, 0xaa, sizeof(password1));
1065 memset(password2, 0xaa, sizeof(password2));
1066 memset(password, 0xaa, pwlen);
1067 fclose(stdout);
1068
1069 if (regenerate)
1070 exit(0);
1071
1072 /* create new hash file */
1073 fprintf(stderr, "Creating '%s' ...\n", fnout);
1074 fntmp = (char *) malloc(strlen(fnout)+strlen(tmpsuffix)+1);
1075 if (!fntmp) abort();
1076 strcpy(fntmp, fnout);
1077 strcat(fntmp, tmpsuffix);
1078 f = fopen(fntmp, "w");
1079 if (!f) {
1080 fprintf(stderr, "Can't write to '%s", fntmp);
1081 perror("'");
1082 exit(1);
1083 }
1084 if (fchmod(fileno(f), S_IRUSR | S_IWUSR)) {
1085 fprintf(stderr, "Can't fchmod '%s", fntmp);
1086 perror("'");
1087 exit(1);
1088 }
1089
1090 /* write magic code for format identification */
1091 fprintf(f, "%s", otpw_magic);
1092 fprintf(f, "%d %d %d %d\n", pages * rows * cols, challen, otpw_hlen,
1093 pwchars);
1094
1095 /* output all hash values in random permutation order */
1096 if (random_order) {
1097 for (k = pages * rows * cols - 1; k >= 0; k--) {
1098 rbg_iter(r);
1099 i = k > 0 ? (*(unsigned *) r) % k : 0;
1100 fprintf(f, "%s\n", hbuf + i*hbuflen);
1101 memcpy(hbuf + i*hbuflen, hbuf + k*hbuflen, hbuflen);
1102 }
1103 } else {
1104 for (k = 0; k < pages * rows * cols; k++)
1105 fprintf(f, "%s\n", hbuf + k*hbuflen);
1106 }
1107
1108 fclose(f);
1109 if (rename(fntmp, fnout)) {
1110 fprintf(stderr, "Can't rename '%s' to '%s", fntmp, fnout);
1111 perror("'");
1112 exit(1);
1113 }
1114 free(fntmp);
1115
1116 /* if we overwrite OTPW file, then any remaining lock is now meaningless */
1117 unlock:
1118 fntmp = (char *) malloc(strlen(fnout)+strlen(otpw_locksuffix)+1);
1119 if (!fntmp) abort();
1120 strcpy(fntmp, fnout);
1121 strcat(fntmp, otpw_locksuffix);
1122 if (unlink(fntmp)) {
1123 if (errno != ENOENT) {
1124 fprintf(stderr, "Can't delete lock file '%s", fntmp);
1125 perror("'");
1126 exit(1);
1127 }
1128 } else {
1129 fprintf(stderr, "Deleted lock file '%s'\n", fntmp);
1130 }
1131 free(fntmp);
1132
1133 return 0;
1134 }
1135