long auth_gssapi_client (authchallenge_t challenger,authrespond_t responder, char *service,NETMBX *mb,void *stream, unsigned long *trial,char *user) { gss_buffer_desc chal; kinit_t ki = (kinit_t) mail_parameters (NIL,GET_KINIT,NIL); long ret = NIL; *trial = 65535; /* never retry */ /* get initial (empty) challenge */ if ((chal.value = (*challenger) (stream,(unsigned long *) &chal.length)) != NULL) { if (chal.length) { /* abort if challenge non-empty */ mm_log ("Server bug: non-empty initial GSSAPI challenge",WARN); (*responder) (stream,NIL,0); ret = LONGT; /* will get a BAD response back */ } else if (mb->authuser[0] && strcmp (mb->authuser,myusername ())) { mm_log ("Can't use Kerberos: invalid /authuser",WARN); (*responder) (stream,NIL,0); ret = LONGT; /* will get a BAD response back */ } else ret = auth_gssapi_client_work (challenger,chal,responder,service,mb, stream,user,ki); } return ret; }
char *kerberos_login (char *user,char *authuser,int argc,char *argv[]) { krb5_context ctx; krb5_principal prnc; char kuser[NETMAXUSER]; char *ret = NIL; /* make a context */ if (!krb5_init_context (&ctx)) { /* build principal */ if (!krb5_parse_name (ctx,authuser,&prnc)) { /* can get local name for this principal? */ if (!krb5_aname_to_localname (ctx,prnc,NETMAXUSER-1,kuser)) { /* yes, local name permitted login as user? */ if (authserver_login (user,kuser,argc,argv) || authserver_login (lcase (user),kuser,argc,argv)) ret = myusername (); /* yes, return user name */ } krb5_free_principal (ctx,prnc); } krb5_free_context (ctx); /* finished with context */ } return ret; }
sfs_connect_t * sfs_connect_srp (str u, srp_client *srpp, sfs_connect_cb cb, str *userp, str *pwdp, bool *serverokp) { static rxx usrhost ("^([^@]+)?@(.*)$"); if (!usrhost.match (u)) { if (userp) *userp = u; (*cb) (NULL, "not of form [user]@hostname"); return NULL; } str user (usrhost[1]), host (usrhost[2]); if (!user && !(user = myusername ())) { (*cb) (NULL, "could not get local username"); return NULL; } ref<sfssrp_authorizer> a (New refcounted<sfssrp_authorizer>); a->srpc = srpp; sfs_connect_t *cs = New sfs_connect_t (wrap (sfs_connect_srp_2, sfs::bundle_t<ref<sfssrp_authorizer>, str *, str *> (a, userp, pwdp), serverokp, cb)); cs->sname () = host; cs->service () = SFS_AUTHSERV; cs->encrypt = true; cs->check_hostid = false; cs->authorizer = a; cs->aarg.user = user; if (!cs->start ()) return NULL; return cs; }
TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf) { TCPSTREAM *stream = NIL; void *adr; char host[MAILTMPLEN],tmp[MAILTMPLEN],*path,*argv[MAXARGV+1],*r; int i,ti,pipei[2],pipeo[2]; size_t len; time_t now; struct timeval tmo; fd_set fds,efds; blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); #ifdef SSHPATH /* ssh path defined yet? */ if (!sshpath) sshpath = cpystr (SSHPATH); #endif #ifdef RSHPATH /* rsh path defined yet? */ if (!rshpath) rshpath = cpystr (RSHPATH); #endif if (*service == '*') { /* want ssh? */ /* return immediately if ssh disabled */ if (!(sshpath && (ti = sshtimeout))) return NIL; /* ssh command prototype defined yet? */ if (!sshcommand) sshcommand = cpystr ("%s %s -l %s exec /etc/r%sd"); } /* want rsh? */ else if (rshpath && (ti = rshtimeout)) { /* rsh command prototype defined yet? */ if (!rshcommand) rshcommand = cpystr ("%s %s -l %s exec /etc/r%sd"); } else return NIL; /* rsh disabled */ /* look like domain literal? */ if (mb->host[0] == '[' && mb->host[i = (strlen (mb->host))-1] == ']') { strcpy (host,mb->host+1); /* yes, copy without brackets */ host[i-1] = '\0'; /* validate domain literal */ if (adr = ip_stringtoaddr (host,&len,&i)) fs_give ((void **) &adr); else { sprintf (tmp,"Bad format domain-literal: %.80s",host); mm_log (tmp,ERROR); return NIL; } } else strcpy (host,tcp_canonical (mb->host)); if (*service == '*') /* build ssh command */ sprintf (tmp,sshcommand,sshpath,host, mb->user[0] ? mb->user : myusername (),service + 1); else sprintf (tmp,rshcommand,rshpath,host, mb->user[0] ? mb->user : myusername (),service); if (tcpdebug) { char msg[MAILTMPLEN]; sprintf (msg,"Trying %.100s",tmp); mm_log (msg,TCPDEBUG); } /* parse command into argv */ for (i = 1,path = argv[0] = strtok_r (tmp," ",&r); (i < MAXARGV) && (argv[i] = strtok_r (NIL," ",&r)); i++); argv[i] = NIL; /* make sure argv tied off */ /* make command pipes */ if (pipe (pipei) < 0) return NIL; if ((pipei[0] >= FD_SETSIZE) || (pipei[1] >= FD_SETSIZE) || (pipe (pipeo) < 0)) { close (pipei[0]); close (pipei[1]); return NIL; } (*bn) (BLOCK_TCPOPEN,NIL); /* quell alarm up here for NeXT */ if ((pipeo[0] >= FD_SETSIZE) || (pipeo[1] >= FD_SETSIZE) || ((i = fork ()) < 0)) { /* make inferior process */ close (pipei[0]); close (pipei[1]); close (pipeo[0]); close (pipeo[1]); (*bn) (BLOCK_NONE,NIL); return NIL; } if (!i) { /* if child */ alarm (0); /* never have alarms in children */ if (!fork ()) { /* make grandchild so it's inherited by init */ int cf; /* don't alter parent vars in case vfork() */ int maxfd = max (20,max (max(pipei[0],pipei[1]),max(pipeo[0],pipeo[1]))); dup2 (pipei[1],1); /* parent's input is my output */ dup2 (pipei[1],2); /* parent's input is my error output too */ dup2 (pipeo[0],0); /* parent's output is my input */ /* close all unnecessary descriptors */ for (cf = 3; cf <= maxfd; cf++) close (cf); setpgrp (0,getpid ()); /* be our own process group */ _exit (execv (path,argv));/* now run it */ } _exit (1); /* child is done */ } grim_pid_reap (i,NIL); /* reap child; grandchild now owned by init */ close (pipei[1]); /* close child's side of the pipes */ close (pipeo[0]); /* create TCP/IP stream */ stream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0, sizeof (TCPSTREAM)); /* copy remote host name from argument */ stream->remotehost = cpystr (stream->host = cpystr (host)); stream->tcpsi = pipei[0]; /* init sockets */ stream->tcpso = pipeo[1]; stream->ictr = 0; /* init input counter */ stream->port = 0xffffffff; /* no port number */ ti += now = time (0); /* open timeout */ tmo.tv_usec = 0; /* initialize usec timeout */ FD_ZERO (&fds); /* initialize selection vector */ FD_ZERO (&efds); /* handle errors too */ FD_SET (stream->tcpsi,&fds); /* set bit in selection vector */ FD_SET (stream->tcpsi,&efds); /* set bit in error selection vector */ FD_SET (stream->tcpso,&efds); /* set bit in error selection vector */ do { /* block under timeout */ tmo.tv_sec = ti - now; i = select (max (stream->tcpsi,stream->tcpso)+1,&fds,NIL,&efds,&tmo); now = time (0); /* fake timeout if interrupt & time expired */ if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0; } while ((i < 0) && (errno == EINTR)); if (i <= 0) { /* timeout or error? */ sprintf (tmp,i ? "error in %s to IMAP server" : "%s to IMAP server timed out",(*service == '*') ? "ssh" : "rsh"); mm_log (tmp,WARN); tcp_close (stream); /* punt stream */ stream = NIL; } (*bn) (BLOCK_NONE,NIL); /* return user name */ strcpy (usrbuf,mb->user[0] ? mb->user : myusername ()); return stream; /* return success */ }
int deliver (FILE *f,unsigned long msglen,char *user) { MAILSTREAM *ds = NIL; char *s,*mailbox,tmp[MAILTMPLEN],path[MAILTMPLEN]; STRING st; struct stat sbuf; /* have a mailbox specifier? */ if ((mailbox = strchr (user,'+')) != NULL) { *mailbox++ = '\0'; /* yes, tie off user name */ if (!*mailbox || !compare_cstring ((unsigned char *) mailbox,"INBOX")) mailbox = NIL; /* user+ and user+INBOX same as user */ } if (!*user) user = myusername (); else if (strcmp (user,myusername ())) return fail ("can't deliver to other user",EX_CANTCREAT); sprintf (tmp,"delivering to %.80s+%.80s",user,mailbox ? mailbox : "INBOX"); mm_dlog (tmp); /* prepare stringstruct */ INIT (&st,file_string,(void *) f,msglen); if (mailbox) { /* non-INBOX name */ switch (mailbox[0]) { /* make sure a valid name */ default: /* other names, try to deliver if not INBOX */ if ((strlen (mailbox) <= NETMAXMBX) && !strstr (mailbox,"..") && !strstr (mailbox,"//") && !strstr (mailbox,"/~") && mailboxfile (path,mailbox) && path[0] && !deliver_safely (NIL,&st,mailbox,path,tmp)) return NIL; case '%': case '*': /* wildcards not valid */ case '/': /* absolute path names not valid */ case '~': /* user names not valid */ sprintf (tmp,"invalid mailbox name %.80s+%.80s",user,mailbox); mm_log (tmp,WARN); break; } mm_dlog ("retrying delivery to INBOX"); SETPOS (&st,0); /* rewind stringstruct just in case */ } /* no -I, resolve "INBOX" into path */ if (mailboxfile (path,mailbox = "INBOX") && !path[0]) { /* clear box, get generic INBOX prototype */ if (!(ds = mail_open (NIL,"INBOX",OP_PROTOTYPE))) fatal ("no INBOX prototype"); /* standard system driver? */ if (!strcmp (ds->dtb->name,"unix") || !strcmp (ds->dtb->name,"mmdf")) { strcpy (path,sysinbox ());/* use system INBOX */ if (!lstat (path,&sbuf)) /* deliver to existing system INBOX */ return deliver_safely (ds,&st,mailbox,path,tmp); } else { /* other driver, try ~/INBOX */ if ((mailboxfile (path,"&&&&&") == path) && (s = strstr (path,"&&&&&")) && strcpy (s,"INBOX") && !lstat (path,&sbuf)){ /* deliver to existing ~/INBOX */ sprintf (tmp,"#driver.%s/INBOX",ds->dtb->name); return deliver_safely (ds,&st,cpystr (tmp),path,tmp); } } /* not dummy, deliver to driver imputed path */ if (strcmp (ds->dtb->name,"dummy")) return (ibxpath (ds,&mailbox,path) && !lstat (path,&sbuf)) ? deliver_safely (ds,&st,mailbox,path,tmp) : fail ("unable to resolve INBOX path",EX_CANTCREAT); /* dummy, empty imputed append path exist? */ if (ibxpath (ds = default_proto (T),&mailbox,path) && !lstat (path,&sbuf) && !sbuf.st_size) return deliver_safely (ds,&st,mailbox,path,tmp); /* impute path that we will create */ if (!ibxpath (ds = default_proto (NIL),&mailbox,path)) return fail ("unable to resolve INBOX",EX_CANTCREAT); } /* black box, must create, get create proto */ else if (lstat (path,&sbuf)) ds = default_proto (NIL); else { /* black box, existing file */ /* empty file, get append prototype */ if (!sbuf.st_size) ds = default_proto (T); /* non-empty, get prototype from its data */ else if (!(ds = mail_open (NIL,"INBOX",OP_PROTOTYPE))) fatal ("no INBOX prototype"); /* error if unknown format */ if (!strcmp (ds->dtb->name,"phile")) return fail ("unknown format INBOX",EX_UNAVAILABLE); /* otherwise can deliver to it */ return deliver_safely (ds,&st,mailbox,path,tmp); } sprintf (tmp,"attempting to create mailbox %.80s path %.80s",mailbox,path); mm_dlog (tmp); /* supplicate to the Evil One */ if (!path_create (ds,path)) return fail ("can't create INBOX",EX_CANTCREAT); sprintf (tmp,"created %.80s",path); mm_dlog (tmp); /* deliver the message */ return deliver_safely (ds,&st,mailbox,path,tmp); }
int main (int argc,char *argv[]) { FILE *f = NIL; int c,ret = 0; unsigned long msglen; char *s,tmp[MAILTMPLEN]; uid_t ruid = getuid (); struct passwd *pwd = ruid ? getpwnam ("daemon") : NIL; openlog ("dmail",LOG_PID,LOG_MAIL); /* must not be root or daemon! */ if (!ruid || (pwd && (pwd->pw_uid == ruid))) _exit (fail ("dmail may not be invoked by root or daemon",EX_USAGE)); #include "linkage.c" /* process all flags */ for (--argc; argc && (*(s = *++argv)) == '-'; argc--) switch (s[1]) { case 'D': /* debug */ debug = T; /* extra debugging */ break; case 's': /* deliver as seen */ flagseen = T; break; case 'f': case 'r': /* flag giving return path */ if (sender) _exit (fail ("duplicate -r",EX_USAGE)); if (argc--) sender = cpystr (*++argv); else _exit (fail ("missing argument to -r",EX_USAGE)); break; case 'k': if (keywords) _exit (fail ("duplicate -k",EX_USAGE)); if (argc--) keywords = cpystr (*++argv); else _exit (fail ("missing argument to -k",EX_USAGE)); break; case 'p': if (s[2] && ((s[2] == '-') || isdigit (s[2]))) precedence = atol (s + 2); else if (argc-- && ((*(s = *++argv) == '-') || isdigit (*s))) precedence = atol (s); else _exit (fail ("missing argument to -p",EX_USAGE)); break; default: /* anything else */ _exit (fail ("unknown switch",EX_USAGE)); } if (argc > 1) _exit (fail ("too many recipients",EX_USAGE)); else if (!(f = tmpfile ())) _exit(fail ("can't make temp file",EX_TEMPFAIL)); /* build delivery headers */ if (sender) fprintf (f,"Return-Path: <%s>\015\012",sender); /* start Received line: */ fprintf (f,"Received: via dmail-%s.%s for %s; ",CCLIENTVERSION,version, (argc == 1) ? *argv : myusername ()); rfc822_date (tmp); fputs (tmp,f); fputs ("\015\012",f); /* copy text from standard input */ if (!fgets (tmp,MAILTMPLEN-1,stdin) || !(s = strchr (tmp,'\n')) || (s == tmp) || s[1]) _exit (fail ("bad first message line",EX_USAGE)); else if (s[-1] == '\015') { /* nuke leading "From " line */ if ((tmp[0] != 'F') || (tmp[1] != 'r') || (tmp[2] != 'o') || (tmp[3] != 'm') || (tmp[4] != ' ')) fputs (tmp,f); while ((c = getchar ()) != EOF) putc (c,f); } else { if ((tmp[0] != 'F') || (tmp[1] != 'r') || (tmp[2] != 'o') || (tmp[3] != 'm') || (tmp[4] != ' ')) { *s++ = '\015'; /* overwrite NL with CRLF */ *s++ = '\012'; *s = '\0'; /* tie off string */ fputs (tmp,f); /* write line */ } } /* copy text from standard input */ while ((c = getchar ()) != EOF) { /* add CR if needed */ if (c == '\012') putc ('\015',f); putc (c,f); } msglen = ftell (f); /* size of message */ fflush (f); /* make sure all changes written out */ if (ferror (f)) ret = fail ("error writing temp file",EX_TEMPFAIL); else if (!msglen) ret = fail ("empty message",EX_TEMPFAIL); /* single delivery */ else ret = deliver (f,msglen,argc ? *argv : myusername ()); fclose (f); /* all done with temporary file */ _exit (ret); /* normal exit */ return 0; /* stupid gcc */ }
char *myhomedir () { if (!myHomeDir) myusername ();/* initialize if first time */ return myHomeDir; }
long auth_gssapi_client_work (authchallenge_t challenger,gss_buffer_desc chal, authrespond_t responder,char *service,NETMBX *mb, void *stream,char *user,kinit_t ki) { char tmp[MAILTMPLEN]; OM_uint32 smj,smn,dsmj,dsmn; OM_uint32 mctx = 0; gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; gss_buffer_desc resp,buf; long i; int conf; gss_qop_t qop; gss_name_t crname = NIL; blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); void *data; long ret = NIL; sprintf (tmp,"%s@%s",service,mb->host); buf.length = strlen (buf.value = tmp); /* get service name */ if (gss_import_name (&smn,&buf,GSS_C_NT_HOSTBASED_SERVICE,&crname) != GSS_S_COMPLETE) { mm_log ("Can't import Kerberos service name",WARN); (*responder) (stream,NIL,0); } else { data = (*bn) (BLOCK_SENSITIVE,NIL); /* negotiate with KDC */ smj = gss_init_sec_context (&smn,GSS_C_NO_CREDENTIAL,&ctx,crname,NIL, GSS_C_INTEG_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,0,GSS_C_NO_CHANNEL_BINDINGS, GSS_C_NO_BUFFER,NIL,&resp,NIL,NIL); (*bn) (BLOCK_NONSENSITIVE,data); /* while continuation needed */ while (smj == GSS_S_CONTINUE_NEEDED) { if (chal.value) fs_give ((void **) &chal.value); /* send response, get next challenge */ i = (*responder) (stream,resp.value,resp.length) && (chal.value = (*challenger) (stream,(unsigned long *) &chal.length)); gss_release_buffer (&smn,&resp); if (i) { /* negotiate continuation with KDC */ data = (*bn) (BLOCK_SENSITIVE,NIL); switch (smj = /* make sure continuation going OK */ gss_init_sec_context (&smn,GSS_C_NO_CREDENTIAL,&ctx, crname,GSS_C_NO_OID,GSS_C_INTEG_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG,0, GSS_C_NO_CHANNEL_BINDINGS,&chal,NIL, &resp,NIL,NIL)) { case GSS_S_CONTINUE_NEEDED: case GSS_S_COMPLETE: break; default: /* error, don't need context any more */ gss_delete_sec_context (&smn,&ctx,NIL); } (*bn) (BLOCK_NONSENSITIVE,data); } else { /* error in continuation */ mm_log ("Error in negotiating Kerberos continuation",WARN); (*responder) (stream,NIL,0); /* don't need context any more */ gss_delete_sec_context (&smn,&ctx,NIL); break; } } switch (smj) { /* done - deal with final condition */ case GSS_S_COMPLETE: if (chal.value) fs_give ((void **) &chal.value); /* get prot mechanisms and max size */ if ((*responder) (stream,resp.value ? resp.value : "",resp.length) && (chal.value = (*challenger) (stream,(unsigned long *)&chal.length))&& (gss_unwrap (&smn,ctx,&chal,&resp,&conf,&qop) == GSS_S_COMPLETE) && (resp.length >= 4) && (*((char *) resp.value) & AUTH_GSSAPI_P_NONE)){ /* make copy of flags and length */ memcpy (tmp,resp.value,4); gss_release_buffer (&smn,&resp); /* no session protection */ tmp[0] = AUTH_GSSAPI_P_NONE; /* install user name */ strcpy (tmp+4,strcpy (user,mb->user[0] ? mb->user : myusername ())); buf.value = tmp; buf.length = strlen (user) + 4; /* successful negotiation */ switch (smj = gss_wrap (&smn,ctx,NIL,qop,&buf,&conf,&resp)) { case GSS_S_COMPLETE: if ((*responder) (stream,resp.value,resp.length)) ret = T; gss_release_buffer (&smn,&resp); break; default: do switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE, GSS_C_NO_OID,&mctx,&resp)) { case GSS_S_COMPLETE: mctx = 0; case GSS_S_CONTINUE_NEEDED: sprintf (tmp,"Unknown gss_wrap failure: %s",(char *) resp.value); mm_log (tmp,WARN); gss_release_buffer (&dsmn,&resp); } while (dsmj == GSS_S_CONTINUE_NEEDED); do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE, GSS_C_NO_OID,&mctx,&resp)) { case GSS_S_COMPLETE: case GSS_S_CONTINUE_NEEDED: sprintf (tmp,"GSSAPI mechanism status: %s",(char *) resp.value); mm_log (tmp,WARN); gss_release_buffer (&dsmn,&resp); } while (dsmj == GSS_S_CONTINUE_NEEDED); (*responder) (stream,NIL,0); } } /* flush final challenge */ if (chal.value) fs_give ((void **) &chal.value); /* don't need context any more */ gss_delete_sec_context (&smn,&ctx,NIL); break; case GSS_S_CREDENTIALS_EXPIRED: if (chal.value) fs_give ((void **) &chal.value); /* retry if application kinits */ if (ki && (*ki) (mb->host,"Kerberos credentials expired")) ret = auth_gssapi_client_work (challenger,chal,responder,service,mb, stream,user,NIL); else { /* application can't kinit */ sprintf (tmp,"Kerberos credentials expired (try running kinit) for %s", mb->host); mm_log (tmp,WARN); (*responder) (stream,NIL,0); } break; case GSS_S_FAILURE: if (chal.value) fs_give ((void **) &chal.value); do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE, GSS_C_NO_OID,&mctx,&resp)) { case GSS_S_COMPLETE: /* end of message, can kinit? */ if (ki && kerberos_try_kinit (smn) && (*ki) (mb->host,(char *) resp.value)) { gss_release_buffer (&dsmn,&resp); ret = auth_gssapi_client_work (challenger,chal,responder,service,mb, stream,user,NIL); break; /* done */ } else (*responder) (stream,NIL,0); case GSS_S_CONTINUE_NEEDED: sprintf (tmp,kerberos_try_kinit (smn) ? "Kerberos error: %.80s (try running kinit) for %.80s" : "GSSAPI failure: %s for %.80s",(char *) resp.value,mb->host); mm_log (tmp,WARN); gss_release_buffer (&dsmn,&resp); } while (dsmj == GSS_S_CONTINUE_NEEDED); break; default: /* miscellaneous errors */ if (chal.value) fs_give ((void **) &chal.value); do switch (dsmj = gss_display_status (&dsmn,smj,GSS_C_GSS_CODE, GSS_C_NO_OID,&mctx,&resp)) { case GSS_S_COMPLETE: mctx = 0; case GSS_S_CONTINUE_NEEDED: sprintf (tmp,"Unknown GSSAPI failure: %s",(char *) resp.value); mm_log (tmp,WARN); gss_release_buffer (&dsmn,&resp); } while (dsmj == GSS_S_CONTINUE_NEEDED); do switch (dsmj = gss_display_status (&dsmn,smn,GSS_C_MECH_CODE, GSS_C_NO_OID,&mctx,&resp)) { case GSS_S_COMPLETE: case GSS_S_CONTINUE_NEEDED: sprintf (tmp,"GSSAPI mechanism status: %s",(char *) resp.value); mm_log (tmp,WARN); gss_release_buffer (&dsmn,&resp); } while (dsmj == GSS_S_CONTINUE_NEEDED); (*responder) (stream,NIL,0); break; } /* finished with credentials name */ if (crname) gss_release_name (&smn,&crname); } return ret; /* return status */ }