/* This file is in the public domain. */ #include #include #include #include #include #include #include #include extern const char *__progname; #include "pp.h" #include "oq.h" #include "bpp.h" #include "str.h" #include "msgs.h" #include "util.h" #include "errf.h" #include "panic.h" #include "config.h" #include "nested.h" #include "verbose.h" #include "pollloop.h" #include "pkt-util.h" #include "stdio-util.h" #include "userauth-conn.h" typedef struct uacs_state UACS_STATE; struct uacs_state { int stage; #define UAS_PRE_SR 1 #define UAS_RUNNING 2 #define UAS_DONE 3 int failures; int tfd; int tid; PKTQ oq; SUASTATE uastate; } ; static void set_attempts(SUASTATE *s) { config_set_int("auth-attempts",s->attempts); config_set_int("auth-alg-attempts",s->curalg?s->curalg->s.attempts:0); } static void *uac_s_init(LAYER *l) { UACS_STATE *s; s = malloc(sizeof(UACS_STATE)); s->stage = UAS_PRE_SR; s->failures = 0; s->tfd = -1; pktq_init(&s->oq); s->uastate.layer = l; s->uastate.curalg = 0; s->uastate.attempts = 0; s->uastate.haveuser = 0; s->uastate.curuser = 0; s->uastate.service = STRZERO; s->uastate.passwd = 0; s->uastate.homedir = 0; s->uastate.shell = 0; set_attempts(&s->uastate); return(s); } static void timed_out(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { logmsg(LM_NOTE|LM_PEER,"authentication timed out"); exit(0); } static void set_timeout(UACS_STATE *s, int seconds) { struct itimerval itv; s->tfd = -1; if (config_bool("one-conn")) return; #ifdef AF_TIMER s->tfd = socket(AF_TIMER,SOCK_STREAM,0); if (s->tfd < 0) { logmsg(LM_ERR|LM_PEER,"socket (AF_TIMER SOCK_STREAM): %s",strerror(errno)); exit(1); } itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; itv.it_value.tv_sec = seconds; itv.it_value.tv_usec = 0; write(s->tfd,&itv,sizeof(itv)); s->tid = add_poll_fd(s->tfd,&rwtest_always,&rwtest_never,&timed_out,0,s); #else (void)&timed_out; /* pacify -Wunused */ (void)itv; (void)seconds; #endif } static void handle_service_request(UACS_STATE *s, LAYER *l) { STR servname; unsigned char *opp; parse_packet(l->b,&pp_fail, PP_IGNORE(1), PP_STRING(&servname), PP_ENDSHERE ); if (! str_equalsC(servname,"ssh-userauth")) { send_disconnect(SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,0,0,0,0); logmsg(LM_NOTE|LM_PEER,"service `%.*s' not available",servname.len,servname.data); exit(0); } l->b->opkt[0] = SSH_MSG_SERVICE_ACCEPT; opp = put_string(&l->b->opkt[1],servname.data,servname.len); l->b->oplen = opp - &l->b->opkt[0]; below_opkt(l); s->stage = UAS_RUNNING; set_timeout(s,600); } void send_failure(SUASTATE *s, int partial) { LAYER *l; FILE *f; char *buf; int n; unsigned char *opp; static int showed = 0; l = s->layer; l->b->opkt[0] = SSH_MSG_USERAUTH_FAILURE; opp = &l->b->opkt[1]; f = fopen_alloc(&buf,&n); if ((VERB(OFFER) && !showed) || VERB(AUTH)) { pverb(VERBOSE_OFFER|VERBOSE_AUTH,"Our authentication algorithms enabled (*=can't use):\n"); } alglist_map(&algs_ua,({ NESTED int foo(void *alg) { char flag; if ((*((UAALG *)alg)->s.canuse)(s)) { fprintf(f,",%s",(*at_ua.name)(alg)); flag = ' '; } else { flag = '*'; } if ((VERB(OFFER) && !showed) || VERB(AUTH)) { pverb(VERBOSE_OFFER|VERBOSE_AUTH,"\t%c %s\n",flag,(*at_ua.name)(alg)); } return(0); } &foo; })); fclose(f); showed = 1; if (n < 1) { opp = put_string(opp,0,0); } else { opp = put_string(opp,buf+1,n-1); } free(buf); *opp++ = partial ? 1 : 0; l->b->oplen = opp - &l->b->opkt[0]; below_opkt(l); } void add_attempt(SUASTATE *s) { s->attempts ++; s->curalg->s.attempts ++; set_attempts(s); } static void handle_userauth_request(UACS_STATE *s, LAYER *l) { STR username; STR servname; STR methname; const void *rest; int restlen; UAALG *alg; int curuid; NESTED void clears(void) { free_str(username); free_str(servname); free_str(methname); } parse_packet(l->b,&pp_fail, PP_IGNORE(1), PP_STRING(&username), PP_STRING(&servname), PP_STRING(&methname), PP_REST(&rest,&restlen) ); if (VERB(PROGRESS) || VERB(AUTH)) { pverb(VERBOSE_PROGRESS|VERBOSE_AUTH,"USERAUTH_REQUEST: %.*s/%.*s/%.*s+%d\n", username.len,username.data, servname.len,servname.data, methname.len,methname.data, restlen); } curuid = getuid(); if ( !s->uastate.haveuser || !str_equalsC(username,s->uastate.curuser) || !str_equalss(servname,s->uastate.service) ) { struct passwd *pw; char *newuser; if (! str_equalsC(servname,"ssh-connection")) { send_disconnect(SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,0,0,0,0); exit(0); } newuser = blk_to_cstr(username.data,username.len); if (s->uastate.haveuser) { logmsg(LM_WARN|LM_PEER,"switched user/service (%s/%.*s -> %s/%.*s)", s->uastate.curuser, s->uastate.service.len,s->uastate.service.data, newuser,servname.len,servname.data); free(s->uastate.curuser); free_str(s->uastate.service); free(s->uastate.passwd); free(s->uastate.homedir); free(s->uastate.shell); } s->uastate.curuser = newuser; username = STRZERO; s->uastate.service = servname; servname = STRZERO; pw = getpwnam(s->uastate.curuser); /* This condition assumes that "can authenticate as anyone" is equivalent to "running as UID 0". This may not be true on systems with decent privilege models instead of the traditional simplistic UNIX division into "root" and "non-root". */ if (pw && (!curuid || (curuid == pw->pw_uid))) { s->uastate.nosuchuser = 0; s->uastate.passwd = strdup(pw->pw_passwd); s->uastate.uid = pw->pw_uid; s->uastate.gid = pw->pw_gid; s->uastate.homedir = strdup(pw->pw_dir); s->uastate.shell = strdup(pw->pw_shell); config_set_str("user",s->uastate.curuser); config_set_bool("user-exists",1); config_set_bool("user-authenticated",0); config_set_int("user-uid",pw->pw_uid); config_set_str("home",s->uastate.homedir); config_set_str("shell",s->uastate.shell); /* XXX do we want to read ~user/.moussh/config here? Think about implications of reading it pre-authentication. */ } else { s->uastate.nosuchuser = 1; s->uastate.passwd = 0; s->uastate.homedir = 0; s->uastate.shell = 0; config_set_str("user",s->uastate.curuser); config_set_bool("user-exists",0); config_set_bool("user-authenticated",0); } s->uastate.haveuser = 1; } alg = (*at_ua.find_rostr)(str_to_rostr(methname)); if (alg && !(*alg->s.canuse)(&s->uastate)) alg = 0; if (alg && !alglist_present(&algs_ua,alg)) alg = 0; if (alg) { config_set_str("auth-alg",alg->name); s->uastate.curalg = alg; set_attempts(&s->uastate); if ((*alg->s.request)(&s->uastate,rest,restlen)) { char *cf; s->stage = UAS_DONE; l->b->opkt[0] = SSH_MSG_USERAUTH_SUCCESS; l->b->oplen = 1; if (s->tfd >= 0) { remove_poll_id(s->tid); close(s->tfd); s->tfd = -1; } below_opkt(l); logmsg(LM_INFO|LM_PEER,"authenticated as %s (%s)",s->uastate.curuser,alg->name); setproctitle("serving %s from %s",s->uastate.curuser,l->b->peer_text); config_set_bool("user-authenticated",1); config_set_str("user",s->uastate.curuser); config_set_str("home",s->uastate.homedir); config_set_str("shell",s->uastate.shell); /* XXX do we want to do this above, when the request first arrives? */ asprintf(&cf,"%s/.moussh/config",s->uastate.homedir); if (! curuid) { /* initgroups before chroot since initgroups reads /etc/group */ initgroups(s->uastate.curuser,s->uastate.gid); if (config_bool("chroot")) { if (chroot(s->uastate.homedir) < 0) { logmsg(LM_ERR|LM_PEER,"chroot %s: %s",s->uastate.homedir,strerror(errno)); exit(1); } free(cf); asprintf(&cf,"/.moussh/config"); } setgid(s->uastate.gid); setuid(s->uastate.uid); config_set_int("uid",s->uastate.uid); } load_config(cf); free(cf); } } else { send_failure(&s->uastate,0); } clears(); } static void uac_s_r(LAYER *l, void *arg) { UACS_STATE *s; BPP *b; s = arg; b = l->b; switch (s->stage) { default: panic("impossible stage"); break; case UAS_PRE_SR: switch (b->ipkt[0]) { case SSH_MSG_SERVICE_REQUEST: handle_service_request(s,l); break; default: badmsg("PRE_SR","type %d",b->ipkt[0]); break; } break; case UAS_RUNNING: switch (b->ipkt[0]) { case SSH_MSG_USERAUTH_REQUEST: handle_userauth_request(s,l); break; default: badmsg("RUNNING","type %d",b->ipkt[0]); break; } break; case UAS_DONE: switch (b->ipkt[0]) { case 50 ... 79: break; default: above_ipkt(l); break; } break; } } static void uac_s_w(LAYER *l, void *arg) { UACS_STATE *s; s = arg; switch (s->stage) { default: pktq_put(&s->oq,&l->b->opkt[0],l->b->oplen); break; case UAS_DONE: below_opkt(l); break; } } LAYERDESC layer_userauth_conn_s = { &uac_s_init, &uac_s_r, &uac_s_w }; SUASTATE *userauth_suastate(LAYER *l) { if (l->desc != &layer_userauth_conn_s) panic("layer wrong"); return(&((UACS_STATE *)l->arg)->uastate); }