plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

apop.c (6353B)


      1 /*
      2  * APOP, CRAM - MD5 challenge/response authentication
      3  *
      4  * The client does not authenticate the server, hence no CAI.
      5  *
      6  * Protocol:
      7  *
      8  *	S -> C:	random@domain
      9  *	C -> S:	user hex-response
     10  *	S -> C:	ok
     11  *
     12  * Note that this is the protocol between factotum and the local
     13  * program, not between the two factotums.  The information
     14  * exchanged here is wrapped in the APOP protocol by the local
     15  * programs.
     16  *
     17  * If S sends "bad [msg]" instead of "ok", that is a hint that the key is bad.
     18  * The protocol goes back to "C -> S: user hex-response".
     19  */
     20 
     21 #include "std.h"
     22 #include "dat.h"
     23 
     24 extern Proto apop, cram;
     25 
     26 static int
     27 apopcheck(Key *k)
     28 {
     29 	if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){
     30 		werrstr("need user and !password attributes");
     31 		return -1;
     32 	}
     33 	return 0;
     34 }
     35 
     36 static int
     37 apopclient(Conv *c)
     38 {
     39 	char *chal, *pw, *res;
     40 	int astype, nchal, npw, ntry, ret;
     41 	uchar resp[MD5dlen];
     42 	Attr *attr;
     43 	DigestState *ds;
     44 	Key *k;
     45 
     46 	chal = nil;
     47 	k = nil;
     48 	res = nil;
     49 	ret = -1;
     50 	attr = c->attr;
     51 
     52 	if(c->proto == &apop)
     53 		astype = AuthApop;
     54 	else if(c->proto == &cram)
     55 		astype = AuthCram;
     56 	else{
     57 		werrstr("bad proto");
     58 		goto out;
     59 	}
     60 
     61 	c->state = "find key";
     62 	k = keyfetch(c, "%A %s", attr, c->proto->keyprompt);
     63 	if(k == nil)
     64 		goto out;
     65 
     66 	c->state = "read challenge";
     67 	if((nchal = convreadm(c, &chal)) < 0)
     68 		goto out;
     69 
     70   	for(ntry=1;; ntry++){
     71 		if(c->attr != attr)
     72 			freeattr(c->attr);
     73 		c->attr = addattrs(copyattr(attr), k->attr);
     74 		if((pw = strfindattr(k->privattr, "!password")) == nil){
     75 			werrstr("key has no password (cannot happen?)");
     76 			goto out;
     77 		}
     78 		npw = strlen(pw);
     79 
     80 		switch(astype){
     81 		case AuthApop:
     82 			ds = md5((uchar*)chal, nchal, nil, nil);
     83 			md5((uchar*)pw, npw, resp, ds);
     84 			break;
     85 		case AuthCram:
     86 			hmac_md5((uchar*)chal, nchal, (uchar*)pw, npw, resp, nil);
     87 			break;
     88 		}
     89 
     90 		/* C->S: APOP user hex-response\n */
     91 		if(ntry == 1)
     92 			c->state = "write user";
     93 		else{
     94 			sprint(c->statebuf, "write user (auth attempt #%d)", ntry);
     95 			c->state = c->statebuf;
     96 		}
     97 		if(convprint(c, "%s", strfindattr(k->attr, "user")) < 0)
     98 			goto out;
     99 
    100 		c->state = "write response";
    101 		if(convprint(c, "%.*H", sizeof resp, resp) < 0)
    102 			goto out;
    103 
    104 		c->state = "read result";
    105 		if(convreadm(c, &res) < 0)
    106 			goto out;
    107 
    108 		if(strcmp(res, "ok") == 0)
    109 			break;
    110 
    111 		if(strncmp(res, "bad ", 4) != 0){
    112 			werrstr("bad result: %s", res);
    113 			goto out;
    114 		}
    115 
    116 		c->state = "replace key";
    117 		if((k = keyreplace(c, k, "%s", res+4)) == nil){
    118 			c->state = "auth failed";
    119 			werrstr("%s", res+4);
    120 			goto out;
    121 		}
    122 		free(res);
    123 		res = nil;
    124 	}
    125 
    126 	werrstr("succeeded");
    127 	ret = 0;
    128 
    129 out:
    130 	keyclose(k);
    131 	free(chal);
    132 	free(res);
    133 	if(c->attr != attr)
    134 		freeattr(attr);
    135 	return ret;
    136 }
    137 
    138 /* shared with auth dialing routines */
    139 typedef struct ServerState ServerState;
    140 struct ServerState
    141 {
    142 	int asfd;
    143 	Key *k;
    144 	Ticketreq tr;
    145 	Ticket t;
    146 	char *dom;
    147 	char *hostid;
    148 };
    149 
    150 enum
    151 {
    152 	APOPCHALLEN = 128
    153 };
    154 
    155 static int apopchal(ServerState*, int, char[APOPCHALLEN]);
    156 static int apopresp(ServerState*, char*, char*);
    157 
    158 static int
    159 apopserver(Conv *c)
    160 {
    161 	char chal[APOPCHALLEN], *user, *resp;
    162 	ServerState s;
    163 	int astype, ret;
    164 	Attr *a;
    165 
    166 	ret = -1;
    167 	user = nil;
    168 	resp = nil;
    169 	memset(&s, 0, sizeof s);
    170 	s.asfd = -1;
    171 
    172 	if(c->proto == &apop)
    173 		astype = AuthApop;
    174 	else if(c->proto == &cram)
    175 		astype = AuthCram;
    176 	else{
    177 		werrstr("bad proto");
    178 		goto out;
    179 	}
    180 
    181 	c->state = "find key";
    182 	if((s.k = plan9authkey(c->attr)) == nil)
    183 		goto out;
    184 
    185 	a = copyattr(s.k->attr);
    186 	a = delattr(a, "proto");
    187 	c->attr = addattrs(c->attr, a);
    188 	freeattr(a);
    189 
    190 	c->state = "authdial";
    191 	s.hostid = strfindattr(s.k->attr, "user");
    192 	s.dom = strfindattr(s.k->attr, "dom");
    193 	if((s.asfd = xioauthdial(nil, s.dom)) < 0){
    194 		werrstr("authdial %s: %r", s.dom);
    195 		goto out;
    196 	}
    197 
    198 	c->state = "authchal";
    199 	if(apopchal(&s, astype, chal) < 0)
    200 		goto out;
    201 
    202 	c->state = "write challenge";
    203 	if(convprint(c, "%s", chal) < 0)
    204 		goto out;
    205 
    206 	for(;;){
    207 		c->state = "read user";
    208 		if(convreadm(c, &user) < 0)
    209 			goto out;
    210 
    211 		c->state = "read response";
    212 		if(convreadm(c, &resp) < 0)
    213 			goto out;
    214 
    215 		c->state = "authwrite";
    216 		switch(apopresp(&s, user, resp)){
    217 		case -1:
    218 			goto out;
    219 		case 0:
    220 			c->state = "write status";
    221 			if(convprint(c, "bad authentication failed") < 0)
    222 				goto out;
    223 			break;
    224 		case 1:
    225 			c->state = "write status";
    226 			if(convprint(c, "ok") < 0)
    227 				goto out;
    228 			goto ok;
    229 		}
    230 		free(user);
    231 		free(resp);
    232 		user = nil;
    233 		resp = nil;
    234 	}
    235 
    236 ok:
    237 	ret = 0;
    238 	c->attr = addcap(c->attr, c->sysuser, &s.t);
    239 
    240 out:
    241 	keyclose(s.k);
    242 	free(user);
    243 	free(resp);
    244 	xioclose(s.asfd);
    245 	return ret;
    246 }
    247 
    248 static int
    249 apopchal(ServerState *s, int astype, char chal[APOPCHALLEN])
    250 {
    251 	char trbuf[TICKREQLEN];
    252 	Ticketreq tr;
    253 
    254 	memset(&tr, 0, sizeof tr);
    255 
    256 	tr.type = astype;
    257 
    258 	if(strlen(s->hostid) >= sizeof tr.hostid){
    259 		werrstr("hostid too long");
    260 		return -1;
    261 	}
    262 	strcpy(tr.hostid, s->hostid);
    263 
    264 	if(strlen(s->dom) >= sizeof tr.authdom){
    265 		werrstr("domain too long");
    266 		return -1;
    267 	}
    268 	strcpy(tr.authdom, s->dom);
    269 
    270 	convTR2M(&tr, trbuf);
    271 	if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
    272 		return -1;
    273 
    274 	if(xioasrdresp(s->asfd, chal, APOPCHALLEN) <= 5)
    275 		return -1;
    276 
    277 	s->tr = tr;
    278 	return 0;
    279 }
    280 
    281 static int
    282 apopresp(ServerState *s, char *user, char *resp)
    283 {
    284 	char tabuf[TICKETLEN+AUTHENTLEN];
    285 	char trbuf[TICKREQLEN];
    286 	int len;
    287 	Authenticator a;
    288 	Ticket t;
    289 	Ticketreq tr;
    290 
    291 	tr = s->tr;
    292 	if(memrandom(tr.chal, CHALLEN) < 0)
    293 		return -1;
    294 
    295 	if(strlen(user) >= sizeof tr.uid){
    296 		werrstr("uid too long");
    297 		return -1;
    298 	}
    299 	strcpy(tr.uid, user);
    300 
    301 	convTR2M(&tr, trbuf);
    302 	if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
    303 		return -1;
    304 
    305 	len = strlen(resp);
    306 	if(xiowrite(s->asfd, resp, len) != len)
    307 		return -1;
    308 
    309 	if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
    310 		return 0;
    311 
    312 	convM2T(tabuf, &t, s->k->priv);
    313 	if(t.num != AuthTs
    314 	|| memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
    315 		werrstr("key mismatch with auth server");
    316 		return -1;
    317 	}
    318 
    319 	convM2A(tabuf+TICKETLEN, &a, t.key);
    320 	if(a.num != AuthAc
    321 	|| memcmp(a.chal, tr.chal, sizeof a.chal) != 0
    322 	|| a.id != 0){
    323 		werrstr("key2 mismatch with auth server");
    324 		return -1;
    325 	}
    326 
    327 	s->t = t;
    328 	return 1;
    329 }
    330 
    331 static Role
    332 apoproles[] =
    333 {
    334 	"client",	apopclient,
    335 	"server",	apopserver,
    336 	0
    337 };
    338 
    339 Proto apop = {
    340 	"apop",
    341 	apoproles,
    342 	"user? !password?",
    343 	apopcheck,
    344 	nil
    345 };
    346 
    347 Proto cram = {
    348 	"cram",
    349 	apoproles,
    350 	"user? !password?",
    351 	apopcheck,
    352 	nil
    353 };