122712Sdist /* 234921Sbostic * Copyright (c) 1983 Eric P. Allman 333731Sbostic * Copyright (c) 1988 Regents of the University of California. 433731Sbostic * All rights reserved. 533731Sbostic * 642829Sbostic * %sccs.include.redist.c% 733731Sbostic */ 822712Sdist 933731Sbostic # include "sendmail.h" 1022712Sdist 1133731Sbostic #ifndef lint 1233731Sbostic #ifdef SMTP 13*55360Seric static char sccsid[] = "@(#)srvrsmtp.c 5.37 (Berkeley) 07/19/92 (with SMTP)"; 1433731Sbostic #else 15*55360Seric static char sccsid[] = "@(#)srvrsmtp.c 5.37 (Berkeley) 07/19/92 (without SMTP)"; 1633731Sbostic #endif 1733731Sbostic #endif /* not lint */ 1833731Sbostic 199339Seric # include <errno.h> 2011728Seric # include <signal.h> 214549Seric 2233731Sbostic # ifdef SMTP 234556Seric 244549Seric /* 254549Seric ** SMTP -- run the SMTP protocol. 264549Seric ** 274549Seric ** Parameters: 284549Seric ** none. 294549Seric ** 304549Seric ** Returns: 314549Seric ** never. 324549Seric ** 334549Seric ** Side Effects: 344549Seric ** Reads commands from the input channel and processes 354549Seric ** them. 364549Seric */ 374549Seric 384549Seric struct cmd 394549Seric { 404549Seric char *cmdname; /* command name */ 414549Seric int cmdcode; /* internal code, see below */ 424549Seric }; 434549Seric 444549Seric /* values for cmdcode */ 454549Seric # define CMDERROR 0 /* bad command */ 464549Seric # define CMDMAIL 1 /* mail -- designate sender */ 474976Seric # define CMDRCPT 2 /* rcpt -- designate recipient */ 484549Seric # define CMDDATA 3 /* data -- send message text */ 499339Seric # define CMDRSET 4 /* rset -- reset state */ 509339Seric # define CMDVRFY 5 /* vrfy -- verify address */ 519339Seric # define CMDHELP 6 /* help -- give usage info */ 529339Seric # define CMDNOOP 7 /* noop -- do nothing */ 539339Seric # define CMDQUIT 8 /* quit -- close connection and die */ 549339Seric # define CMDHELO 9 /* helo -- be polite */ 5536230Skarels # define CMDONEX 10 /* onex -- sending one transaction only */ 5636230Skarels # define CMDVERB 11 /* verb -- go into verbose mode */ 5736230Skarels /* debugging-only commands, only enabled if SMTPDEBUG is defined */ 5836230Skarels # define CMDDBGQSHOW 12 /* showq -- show send queue */ 5936230Skarels # define CMDDBGDEBUG 13 /* debug -- set debug mode */ 604549Seric 614549Seric static struct cmd CmdTab[] = 624549Seric { 634549Seric "mail", CMDMAIL, 644976Seric "rcpt", CMDRCPT, 654549Seric "data", CMDDATA, 664549Seric "rset", CMDRSET, 674549Seric "vrfy", CMDVRFY, 687762Seric "expn", CMDVRFY, 694549Seric "help", CMDHELP, 704549Seric "noop", CMDNOOP, 714549Seric "quit", CMDQUIT, 724976Seric "helo", CMDHELO, 738544Seric "verb", CMDVERB, 749314Seric "onex", CMDONEX, 7536230Skarels /* 7636230Skarels * remaining commands are here only 7736230Skarels * to trap and log attempts to use them 7836230Skarels */ 799339Seric "showq", CMDDBGQSHOW, 808544Seric "debug", CMDDBGDEBUG, 814549Seric NULL, CMDERROR, 824549Seric }; 834549Seric 849339Seric bool InChild = FALSE; /* true if running in a subprocess */ 859378Seric bool OneXact = FALSE; /* one xaction only this run */ 8611146Seric 879339Seric #define EX_QUIT 22 /* special code for QUIT command */ 888544Seric 8955012Seric smtp(e) 9055012Seric register ENVELOPE *e; 914549Seric { 924549Seric register char *p; 938544Seric register struct cmd *c; 944549Seric char *cmd; 9546928Sbostic static char *skipword(); 964549Seric bool hasmail; /* mail command received */ 975003Seric auto ADDRESS *vrfyqueue; 9812612Seric ADDRESS *a; 9930448Seric char *sendinghost; 1008544Seric char inp[MAXLINE]; 10124981Seric char cmdbuf[100]; 1027124Seric extern char Version[]; 10311151Seric extern char *macvalue(); 10412612Seric extern ADDRESS *recipient(); 10524943Seric extern ENVELOPE BlankEnvelope; 10624943Seric extern ENVELOPE *newenvelope(); 1074549Seric 1085003Seric hasmail = FALSE; 1097363Seric if (OutChannel != stdout) 1107363Seric { 1117363Seric /* arrange for debugging output to go to remote host */ 1127363Seric (void) close(1); 1137363Seric (void) dup(fileno(OutChannel)); 1147363Seric } 11555012Seric settime(e); 11624971Seric if (RealHostName != NULL) 11725050Seric { 11825050Seric CurHostName = RealHostName; 11925050Seric setproctitle("srvrsmtp %s", CurHostName); 12025050Seric } 12125050Seric else 12225050Seric { 12325050Seric /* this must be us!! */ 12425050Seric CurHostName = MyHostName; 12525050Seric } 12655012Seric expand("\001e", inp, &inp[sizeof inp], e); 127*55360Seric message("220", "%s", inp); 12824943Seric SmtpPhase = "startup"; 12930448Seric sendinghost = NULL; 1304549Seric for (;;) 1314549Seric { 13212612Seric /* arrange for backout */ 13312612Seric if (setjmp(TopFrame) > 0 && InChild) 13412612Seric finis(); 13512612Seric QuickAbort = FALSE; 13612612Seric HoldErrs = FALSE; 13751951Seric LogUsrErrs = FALSE; 13812612Seric 1397356Seric /* setup for the read */ 14055012Seric e->e_to = NULL; 1414577Seric Errors = 0; 1427275Seric (void) fflush(stdout); 1437356Seric 1447356Seric /* read the input line */ 1457685Seric p = sfgets(inp, sizeof inp, InChannel); 1467356Seric 1477685Seric /* handle errors */ 1487356Seric if (p == NULL) 1497356Seric { 1504549Seric /* end of file, just die */ 15136230Skarels message("421", "%s Lost input channel from %s", 15225050Seric MyHostName, CurHostName); 1534549Seric finis(); 1544549Seric } 1554549Seric 1564549Seric /* clean up end of line */ 1574558Seric fixcrlf(inp, TRUE); 1584549Seric 1594713Seric /* echo command to transcript */ 16055012Seric if (e->e_xfp != NULL) 16155012Seric fprintf(e->e_xfp, "<<< %s\n", inp); 1624713Seric 1634549Seric /* break off command */ 1644549Seric for (p = inp; isspace(*p); p++) 1654549Seric continue; 1664549Seric cmd = p; 16724981Seric for (cmd = cmdbuf; *p != '\0' && !isspace(*p); ) 16824981Seric *cmd++ = *p++; 16924981Seric *cmd = '\0'; 1704549Seric 17125691Seric /* throw away leading whitespace */ 17225691Seric while (isspace(*p)) 17325691Seric p++; 17425691Seric 1754549Seric /* decode command */ 1764549Seric for (c = CmdTab; c->cmdname != NULL; c++) 1774549Seric { 17833725Sbostic if (!strcasecmp(c->cmdname, cmdbuf)) 1794549Seric break; 1804549Seric } 1814549Seric 18251954Seric /* reset errors */ 18351954Seric errno = 0; 18451954Seric 1854549Seric /* process command */ 1864549Seric switch (c->cmdcode) 1874549Seric { 1884976Seric case CMDHELO: /* hello -- introduce yourself */ 18924943Seric SmtpPhase = "HELO"; 19025050Seric setproctitle("%s: %s", CurHostName, inp); 19133725Sbostic if (!strcasecmp(p, MyHostName)) 19214877Seric { 19336230Skarels /* 19436230Skarels * didn't know about alias, 19536230Skarels * or connected to an echo server 19636230Skarels */ 19747570Seric message("553", "%s config error: mail loops back to myself", 19847570Seric MyHostName); 19914877Seric break; 20014877Seric } 20133725Sbostic if (RealHostName != NULL && strcasecmp(p, RealHostName)) 20211146Seric { 20324981Seric char hostbuf[MAXNAME]; 20411146Seric 20524981Seric (void) sprintf(hostbuf, "%s (%s)", p, RealHostName); 20630448Seric sendinghost = newstr(hostbuf); 20711146Seric } 20811146Seric else 20930448Seric sendinghost = newstr(p); 2104997Seric message("250", "%s Hello %s, pleased to meet you", 21136230Skarels MyHostName, sendinghost); 2124976Seric break; 2134976Seric 2144549Seric case CMDMAIL: /* mail -- designate sender */ 21524943Seric SmtpPhase = "MAIL"; 21624943Seric 21711151Seric /* force a sending host even if no HELO given */ 21855012Seric if (RealHostName != NULL && macvalue('s', e) == NULL) 21930448Seric sendinghost = RealHostName; 22011151Seric 2219314Seric /* check for validity of this command */ 2224558Seric if (hasmail) 2234558Seric { 2244558Seric message("503", "Sender already specified"); 2254558Seric break; 2264558Seric } 2279339Seric if (InChild) 2289339Seric { 22936230Skarels errno = 0; 2309339Seric syserr("Nested MAIL command"); 2319339Seric exit(0); 2329339Seric } 2339339Seric 2349339Seric /* fork a subprocess to process this command */ 23555012Seric if (runinchild("SMTP-MAIL", e) > 0) 2369339Seric break; 23755012Seric define('s', sendinghost, e); 23855012Seric define('r', "SMTP", e); 23955012Seric initsys(e); 24055012Seric setproctitle("%s %s: %s", e->e_id, 24125050Seric CurHostName, inp); 2429339Seric 2439339Seric /* child -- go do the processing */ 2444549Seric p = skipword(p, "from"); 2454549Seric if (p == NULL) 2464549Seric break; 24755012Seric setsender(p, e); 2484577Seric if (Errors == 0) 2494549Seric { 2504549Seric message("250", "Sender ok"); 2514549Seric hasmail = TRUE; 2524549Seric } 2539339Seric else if (InChild) 2549339Seric finis(); 2554549Seric break; 2564549Seric 2574976Seric case CMDRCPT: /* rcpt -- designate recipient */ 25824943Seric SmtpPhase = "RCPT"; 25955012Seric setproctitle("%s %s: %s", e->e_id, 26025050Seric CurHostName, inp); 26112612Seric if (setjmp(TopFrame) > 0) 26214785Seric { 26355012Seric e->e_flags &= ~EF_FATALERRS; 26412612Seric break; 26514785Seric } 26612612Seric QuickAbort = TRUE; 26751951Seric LogUsrErrs = TRUE; 2684549Seric p = skipword(p, "to"); 2694549Seric if (p == NULL) 2704549Seric break; 27155012Seric a = parseaddr(p, (ADDRESS *) NULL, 1, '\0', e); 27212612Seric if (a == NULL) 27312612Seric break; 27416886Seric a->q_flags |= QPRIMARY; 27555012Seric a = recipient(a, &e->e_sendqueue, e); 27612612Seric if (Errors != 0) 27712612Seric break; 27812612Seric 27912612Seric /* no errors during parsing, but might be a duplicate */ 28055012Seric e->e_to = p; 28112612Seric if (!bitset(QBADADDR, a->q_flags)) 28212612Seric message("250", "Recipient ok"); 28312612Seric else 2844549Seric { 28512612Seric /* punt -- should keep message in ADDRESS.... */ 28612612Seric message("550", "Addressee unknown"); 2874549Seric } 28855012Seric e->e_to = NULL; 2894549Seric break; 2904549Seric 2914549Seric case CMDDATA: /* data -- text of mail */ 29224943Seric SmtpPhase = "DATA"; 2934976Seric if (!hasmail) 2944549Seric { 2954976Seric message("503", "Need MAIL command"); 2964976Seric break; 2974549Seric } 29855012Seric else if (e->e_nrcpts <= 0) 2994549Seric { 3004976Seric message("503", "Need RCPT (recipient)"); 3014976Seric break; 3024549Seric } 3034976Seric 3044976Seric /* collect the text of the message */ 30524943Seric SmtpPhase = "collect"; 30655012Seric setproctitle("%s %s: %s", e->e_id, 30725050Seric CurHostName, inp); 30855012Seric collect(TRUE, e); 3094976Seric if (Errors != 0) 3104976Seric break; 3114976Seric 3128238Seric /* 3138238Seric ** Arrange to send to everyone. 3148238Seric ** If sending to multiple people, mail back 3158238Seric ** errors rather than reporting directly. 3168238Seric ** In any case, don't mail back errors for 3178238Seric ** anything that has happened up to 3188238Seric ** now (the other end will do this). 31910197Seric ** Truncate our transcript -- the mail has gotten 32010197Seric ** to us successfully, and if we have 32110197Seric ** to mail this back, it will be easier 32210197Seric ** on the reader. 3238238Seric ** Then send to everyone. 3248238Seric ** Finally give a reply code. If an error has 3258238Seric ** already been given, don't mail a 3268238Seric ** message back. 3279339Seric ** We goose error returns by clearing error bit. 3288238Seric */ 3298238Seric 33024943Seric SmtpPhase = "delivery"; 33155012Seric if (e->e_nrcpts != 1) 3329378Seric { 3339378Seric HoldErrs = TRUE; 33416886Seric ErrorMode = EM_MAIL; 3359378Seric } 33655012Seric e->e_flags &= ~EF_FATALERRS; 33755012Seric e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp); 3384976Seric 3394976Seric /* send to all recipients */ 34055012Seric sendall(e, SM_DEFAULT); 34155012Seric e->e_to = NULL; 3424976Seric 34323516Seric /* save statistics */ 34455012Seric markstats(e, (ADDRESS *) NULL); 34523516Seric 3468238Seric /* issue success if appropriate and reset */ 3478238Seric if (Errors == 0 || HoldErrs) 3489283Seric message("250", "Ok"); 3498238Seric else 35055012Seric e->e_flags &= ~EF_FATALERRS; 3519339Seric 3529339Seric /* if in a child, pop back to our parent */ 3539339Seric if (InChild) 3549339Seric finis(); 35524943Seric 35624943Seric /* clean up a bit */ 35724943Seric hasmail = 0; 35855012Seric dropenvelope(e); 35955012Seric CurEnv = e = newenvelope(e); 36055012Seric e->e_flags = BlankEnvelope.e_flags; 3614549Seric break; 3624549Seric 3634549Seric case CMDRSET: /* rset -- reset state */ 3644549Seric message("250", "Reset state"); 3659339Seric if (InChild) 3669339Seric finis(); 3679339Seric break; 3684549Seric 3694549Seric case CMDVRFY: /* vrfy -- verify address */ 37055012Seric if (runinchild("SMTP-VRFY", e) > 0) 3719339Seric break; 37225050Seric setproctitle("%s: %s", CurHostName, inp); 37355173Seric #ifdef LOG 37455173Seric if (LogLevel >= 9) 37555173Seric syslog(LOG_INFO, "%s: %s", CurHostName, inp); 37655173Seric #endif 3775003Seric vrfyqueue = NULL; 3787762Seric QuickAbort = TRUE; 37955012Seric sendtolist(p, (ADDRESS *) NULL, &vrfyqueue, e); 3807762Seric if (Errors != 0) 3819339Seric { 3829339Seric if (InChild) 3839339Seric finis(); 3847762Seric break; 3859339Seric } 3865003Seric while (vrfyqueue != NULL) 3875003Seric { 3885003Seric register ADDRESS *a = vrfyqueue->q_next; 3895003Seric char *code; 3905003Seric 3917685Seric while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 3925003Seric a = a->q_next; 3935003Seric 3947685Seric if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 3955003Seric { 3965003Seric if (a != NULL) 3975003Seric code = "250-"; 3985003Seric else 3995003Seric code = "250"; 4005003Seric if (vrfyqueue->q_fullname == NULL) 4015003Seric message(code, "<%s>", vrfyqueue->q_paddr); 4025003Seric else 4035003Seric message(code, "%s <%s>", 4045003Seric vrfyqueue->q_fullname, vrfyqueue->q_paddr); 4055003Seric } 4065003Seric else if (a == NULL) 4075003Seric message("554", "Self destructive alias loop"); 4085003Seric vrfyqueue = a; 4095003Seric } 4109339Seric if (InChild) 4119339Seric finis(); 4124549Seric break; 4134549Seric 4144549Seric case CMDHELP: /* help -- give user info */ 4154577Seric help(p); 4164549Seric break; 4174549Seric 4184549Seric case CMDNOOP: /* noop -- do nothing */ 4194549Seric message("200", "OK"); 4204549Seric break; 4214549Seric 4224549Seric case CMDQUIT: /* quit -- leave mail */ 42325050Seric message("221", "%s closing connection", MyHostName); 4249339Seric if (InChild) 4259339Seric ExitStat = EX_QUIT; 4264549Seric finis(); 4274549Seric 4288544Seric case CMDVERB: /* set verbose mode */ 4298544Seric Verbose = TRUE; 43025025Seric SendMode = SM_DELIVER; 4318544Seric message("200", "Verbose mode"); 4328544Seric break; 4338544Seric 4349314Seric case CMDONEX: /* doing one transaction only */ 4359378Seric OneXact = TRUE; 4369314Seric message("200", "Only one transaction"); 4379314Seric break; 4389314Seric 43936230Skarels # ifdef SMTPDEBUG 4409339Seric case CMDDBGQSHOW: /* show queues */ 4416907Seric printf("Send Queue="); 44255012Seric printaddr(e->e_sendqueue, TRUE); 4435003Seric break; 4447275Seric 4457275Seric case CMDDBGDEBUG: /* set debug mode */ 4467676Seric tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 4477676Seric tTflag(p); 4487676Seric message("200", "Debug set"); 4497275Seric break; 4507275Seric 45136230Skarels # else /* not SMTPDEBUG */ 45224945Seric 45336230Skarels case CMDDBGQSHOW: /* show queues */ 45436230Skarels case CMDDBGDEBUG: /* set debug mode */ 45536233Skarels # ifdef LOG 45636233Skarels if (RealHostName != NULL && LogLevel > 0) 45736230Skarels syslog(LOG_NOTICE, 45836230Skarels "\"%s\" command from %s (%s)\n", 45936230Skarels c->cmdname, RealHostName, 46036230Skarels inet_ntoa(RealHostAddr.sin_addr)); 46136233Skarels # endif 46236230Skarels /* FALL THROUGH */ 46336230Skarels # endif /* SMTPDEBUG */ 46436230Skarels 4654549Seric case CMDERROR: /* unknown command */ 4664549Seric message("500", "Command unrecognized"); 4674549Seric break; 4684549Seric 4694549Seric default: 47036230Skarels errno = 0; 4714549Seric syserr("smtp: unknown code %d", c->cmdcode); 4724549Seric break; 4734549Seric } 4744549Seric } 4754549Seric } 4764549Seric /* 4774549Seric ** SKIPWORD -- skip a fixed word. 4784549Seric ** 4794549Seric ** Parameters: 4804549Seric ** p -- place to start looking. 4814549Seric ** w -- word to skip. 4824549Seric ** 4834549Seric ** Returns: 4844549Seric ** p following w. 4854549Seric ** NULL on error. 4864549Seric ** 4874549Seric ** Side Effects: 4884549Seric ** clobbers the p data area. 4894549Seric */ 4904549Seric 4914549Seric static char * 4924549Seric skipword(p, w) 4934549Seric register char *p; 4944549Seric char *w; 4954549Seric { 4964549Seric register char *q; 4974549Seric 4984549Seric /* find beginning of word */ 4994549Seric while (isspace(*p)) 5004549Seric p++; 5014549Seric q = p; 5024549Seric 5034549Seric /* find end of word */ 5044549Seric while (*p != '\0' && *p != ':' && !isspace(*p)) 5054549Seric p++; 5064549Seric while (isspace(*p)) 5074549Seric *p++ = '\0'; 5084549Seric if (*p != ':') 5094549Seric { 5104549Seric syntax: 5114549Seric message("501", "Syntax error"); 5124549Seric Errors++; 5134549Seric return (NULL); 5144549Seric } 5154549Seric *p++ = '\0'; 5164549Seric while (isspace(*p)) 5174549Seric p++; 5184549Seric 5194549Seric /* see if the input word matches desired word */ 52033725Sbostic if (strcasecmp(q, w)) 5214549Seric goto syntax; 5224549Seric 5234549Seric return (p); 5244549Seric } 5254577Seric /* 5264577Seric ** HELP -- implement the HELP command. 5274577Seric ** 5284577Seric ** Parameters: 5294577Seric ** topic -- the topic we want help for. 5304577Seric ** 5314577Seric ** Returns: 5324577Seric ** none. 5334577Seric ** 5344577Seric ** Side Effects: 5354577Seric ** outputs the help file to message output. 5364577Seric */ 5374577Seric 5384577Seric help(topic) 5394577Seric char *topic; 5404577Seric { 5414577Seric register FILE *hf; 5424577Seric int len; 5434577Seric char buf[MAXLINE]; 5444577Seric bool noinfo; 5454577Seric 5468269Seric if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 5474577Seric { 5484577Seric /* no help */ 54911931Seric errno = 0; 5504577Seric message("502", "HELP not implemented"); 5514577Seric return; 5524577Seric } 5534577Seric 55449669Seric if (topic == NULL || *topic == '\0') 55549669Seric topic = "smtp"; 55649669Seric else 55749669Seric makelower(topic); 55849669Seric 5594577Seric len = strlen(topic); 5604577Seric noinfo = TRUE; 5614577Seric 5624577Seric while (fgets(buf, sizeof buf, hf) != NULL) 5634577Seric { 5644577Seric if (strncmp(buf, topic, len) == 0) 5654577Seric { 5664577Seric register char *p; 5674577Seric 5684577Seric p = index(buf, '\t'); 5694577Seric if (p == NULL) 5704577Seric p = buf; 5714577Seric else 5724577Seric p++; 5734577Seric fixcrlf(p, TRUE); 5744577Seric message("214-", p); 5754577Seric noinfo = FALSE; 5764577Seric } 5774577Seric } 5784577Seric 5794577Seric if (noinfo) 5804577Seric message("504", "HELP topic unknown"); 5814577Seric else 5824577Seric message("214", "End of HELP info"); 5834628Seric (void) fclose(hf); 5844577Seric } 5858544Seric /* 5869339Seric ** RUNINCHILD -- return twice -- once in the child, then in the parent again 5879339Seric ** 5889339Seric ** Parameters: 5899339Seric ** label -- a string used in error messages 5909339Seric ** 5919339Seric ** Returns: 5929339Seric ** zero in the child 5939339Seric ** one in the parent 5949339Seric ** 5959339Seric ** Side Effects: 5969339Seric ** none. 5979339Seric */ 5988544Seric 59955012Seric runinchild(label, e) 6009339Seric char *label; 60155012Seric register ENVELOPE *e; 6029339Seric { 6039339Seric int childpid; 6049339Seric 60516158Seric if (!OneXact) 6069339Seric { 60716158Seric childpid = dofork(); 60816158Seric if (childpid < 0) 60916158Seric { 61016158Seric syserr("%s: cannot fork", label); 61116158Seric return (1); 61216158Seric } 61316158Seric if (childpid > 0) 61416158Seric { 61516158Seric auto int st; 6169339Seric 61716158Seric /* parent -- wait for child to complete */ 61816158Seric st = waitfor(childpid); 61916158Seric if (st == -1) 62016158Seric syserr("%s: lost child", label); 6219339Seric 62216158Seric /* if we exited on a QUIT command, complete the process */ 62316158Seric if (st == (EX_QUIT << 8)) 62416158Seric finis(); 6259339Seric 62616158Seric return (1); 62716158Seric } 62816158Seric else 62916158Seric { 63016158Seric /* child */ 63116158Seric InChild = TRUE; 63225050Seric QuickAbort = FALSE; 63355012Seric clearenvelope(e, FALSE); 63416158Seric } 6359339Seric } 63615256Seric 63716158Seric /* open alias database */ 63855012Seric initaliases(AliasFile, FALSE, e); 63916158Seric 64016158Seric return (0); 6419339Seric } 6429339Seric 6435181Seric # endif SMTP 644