~budgester/irm/trunk

1 by budgester at budgester
Initial import of IRM codebase
1
<?php
2
/*
3
 * pop3.php
4
 *
5
 * @(#) $Header: /home/mlemos/cvsroot/pop3/pop3.php,v 1.15 2005/10/18 17:43:25 mlemos Exp $
6
 *
7
 */
8
9
class pop3_class
10
{
11
	var $hostname="";
12
	var $port=110;
13
	var $quit_handshake=0;
14
	var $error="";
15
	var $authentication_mechanism="USER";
16
	var $realm="";
17
	var $workstation="";
18
	var $join_continuation_header_lines=1;
19
20
	/* Private variables - DO NOT ACCESS */
21
22
	var $connection=0;
23
	var $state="DISCONNECTED";
24
	var $greeting="";
25
	var $must_update=0;
26
	var $debug=0;
27
	var $html_debug=0;
28
	var $next_token="";
29
30
	/* Private methods - DO NOT CALL */
31
32
	Function Tokenize($string,$separator="")
33
	{
34
		if(!strcmp($separator,""))
35
		{
36
			$separator=$string;
37
			$string=$this->next_token;
38
		}
39
		for($character=0;$character<strlen($separator);$character++)
40
		{
41
			if(GetType($position=strpos($string,$separator[$character]))=="integer")
42
				$found=(IsSet($found) ? min($found,$position) : $position);
43
		}
44
		if(IsSet($found))
45
		{
46
			$this->next_token=substr($string,$found+1);
47
			return(substr($string,0,$found));
48
		}
49
		else
50
		{
51
			$this->next_token="";
52
			return($string);
53
		}
54
	}
55
56
	Function SetError($error)
57
	{
58
		return($this->error=$error);
59
	}
60
61
	Function OutputDebug($message)
62
	{
63
		$message.="\n";
64
		if($this->html_debug)
65
			$message=str_replace("\n","<br />\n",HtmlEntities($message));
66
		echo $message;
67
		flush();
68
	}
69
70
	Function GetLine()
71
	{
72
		for($line="";;)
73
		{
74
			if(feof($this->connection))
75
				return(0);
76
			$line.=fgets($this->connection,100);
77
			$length=strlen($line);
78
			if($length>=2
79
			&& substr($line,$length-2,2)=="\r\n")
80
			{
81
				$line=substr($line,0,$length-2);
82
				if($this->debug)
83
					$this->OutputDebug("S $line");
84
				return($line);
85
			}
86
		}
87
	}
88
89
	Function PutLine($line)
90
	{
91
		if($this->debug)
92
			$this->OutputDebug("C $line");
93
		return(fputs($this->connection,"$line\r\n"));
94
	}
95
96
	Function OpenConnection()
97
	{
98
		if($this->hostname=="")
99
			return($this->SetError("2 it was not specified a valid hostname"));
100
		if($this->debug)
101
			$this->OutputDebug("Connecting to ".$this->hostname." ...");
102
		if(($this->connection=@fsockopen($this->hostname,$this->port,$error))==0)
103
		{
104
			switch($error)
105
			{
106
				case -3:
107
					return($this->SetError("-3 socket could not be created"));
108
				case -4:
109
					return($this->SetError("-4 dns lookup on hostname \"$hostname\" failed"));
110
				case -5:
111
					return($this->SetError("-5 connection refused or timed out"));
112
				case -6:
113
					return($this->SetError("-6 fdopen() call failed"));
114
				case -7:
115
					return($this->SetError("-7 setvbuf() call failed"));
116
				default:
117
					return($this->SetError($error." could not connect to the host \"".$this->hostname."\""));
118
			}
119
		}
120
		return("");
121
	}
122
123
	Function CloseConnection()
124
	{
125
		if($this->debug)
126
			$this->OutputDebug("Closing connection.");
127
		if($this->connection!=0)
128
		{
129
			fclose($this->connection);
130
			$this->connection=0;
131
		}
132
	}
133
134
	/* Public methods */
135
136
	/* Open method - set the object variable $hostname to the POP3 server address. */
137
138
	Function Open()
139
	{
140
		if($this->state!="DISCONNECTED")
141
			return($this->SetError("1 a connection is already opened"));
142
		if(($error=$this->OpenConnection())!="")
143
			return($error);
144
		$this->greeting=$this->GetLine();
145
		if(GetType($this->greeting)!="string"
146
		|| $this->Tokenize($this->greeting," ")!="+OK")
147
		{
148
			$this->CloseConnection();
149
			return($this->SetError("3 POP3 server greeting was not found"));
150
		}
151
		$this->Tokenize("<");
152
		$this->must_update=0;
153
		$this->state="AUTHORIZATION";
154
		return("");
155
	}
156
157
	/* Close method - this method must be called at least if there are any
158
     messages to be deleted */
159
160
	Function Close()
161
	{
162
		if($this->state=="DISCONNECTED")
163
			return($this->SetError("no connection was opened"));
164
		if($this->must_update
165
		|| $this->quit_handshake)
166
		{
167
			if($this->PutLine("QUIT")==0)
168
				return($this->SetError("Could not send the QUIT command"));
169
			$response=$this->GetLine();
170
			if(GetType($response)!="string")
171
				return($this->SetError("Could not get quit command response"));
172
			if($this->Tokenize($response," ")!="+OK")
173
				return($this->SetError("Could not quit the connection: ".$this->Tokenize("\r\n")));
174
		}
175
		$this->CloseConnection();
176
		$this->state="DISCONNECTED";
177
		return("");
178
	}
179
180
	/* Login method - pass the user name and password of POP account.  Set
181
     $apop to 1 or 0 wether you want to login using APOP method or not.  */
182
183
	Function Login($user,$password,$apop=0)
184
	{
185
		if($this->state!="AUTHORIZATION")
186
			return($this->SetError("connection is not in AUTHORIZATION state"));
187
		if($apop)
188
		{
189
			if(!strcmp($this->greeting,""))
190
				return($this->SetError("Server does not seem to support APOP authentication"));
191
			if($this->PutLine("APOP $user ".md5("<".$this->greeting.">".$password))==0)
192
				return($this->SetError("Could not send the APOP command"));
193
			$response=$this->GetLine();
194
			if(GetType($response)!="string")
195
				return($this->SetError("Could not get APOP login command response"));
196
			if($this->Tokenize($response," ")!="+OK")
197
				return($this->SetError("APOP login failed: ".$this->Tokenize("\r\n")));
198
		}
199
		else
200
		{
201
			$authenticated=0;
202
			if(strcmp($this->authentication_mechanism,"USER")
203
			&& function_exists("class_exists")
204
			&& class_exists("sasl_client_class"))
205
			{
206
				if(strlen($this->authentication_mechanism))
207
					$mechanisms=array($this->authentication_mechanism);
208
				else
209
				{
210
					$mechanisms=array();
211
					if($this->PutLine("CAPA")==0)
212
						return($this->SetError("Could not send the CAPA command"));
213
					$response=$this->GetLine();
214
					if(GetType($response)!="string")
215
						return($this->SetError("Could not get CAPA command response"));
216
					if(!strcmp($this->Tokenize($response," "),"+OK"))
217
					{
218
						for(;;)
219
						{
220
							$response=$this->GetLine();
221
							if(GetType($response)!="string")
222
								return($this->SetError("Could not retrieve the supported authentication methods"));
223
							switch($this->Tokenize($response," "))
224
							{
225
								case ".":
226
									break 2;
227
								case "SASL":
228
									for($method=1;strlen($mechanism=$this->Tokenize(" "));$method++)
229
										$mechanisms[]=$mechanism;
230
									break;
231
							}
232
						}
233
					}
234
				}
235
				$sasl=new sasl_client_class;
236
				$sasl->SetCredential("user",$user);
237
				$sasl->SetCredential("password",$password);
238
				if(strlen($this->realm))
239
					$sasl->SetCredential("realm",$this->realm);
240
				if(strlen($this->workstation))
241
					$sasl->SetCredential("workstation",$this->workstation);
242
				do
243
				{
244
					$status=$sasl->Start($mechanisms,$message,$interactions);
245
				}
246
				while($status==SASL_INTERACT);
247
				switch($status)
248
				{
249
					case SASL_CONTINUE:
250
						break;
251
					case SASL_NOMECH:
252
						if(strlen($this->authentication_mechanism))
253
							return($this->SetError("authenticated mechanism ".$this->authentication_mechanism." may not be used: ".$sasl->error));
254
						break;
255
					default:
256
						return($this->SetError("Could not start the SASL authentication client: ".$sasl->error));
257
				}
258
				if(strlen($sasl->mechanism))
259
				{
260
					if($this->PutLine("AUTH ".$sasl->mechanism.(IsSet($message) ? " ".base64_encode($message) : ""))==0)
261
						return("Could not send the AUTH command");
262
					$response=$this->GetLine();
263
					if(GetType($response)!="string")
264
						return("Could not get AUTH command response");
265
					switch($this->Tokenize($response," "))
266
					{
267
						case "+OK":
268
							$response="";
269
							break;
270
						case "+":
271
							$response=base64_decode($this->Tokenize("\r\n"));
272
							break;
273
						default:
274
							return($this->SetError("Authentication error: ".$this->Tokenize("\r\n")));
275
					}
276
					for(;!$authenticated;)
277
					{
278
						do
279
						{
280
							$status=$sasl->Step($response,$message,$interactions);
281
						}
282
						while($status==SASL_INTERACT);
283
						switch($status)
284
						{
285
							case SASL_CONTINUE:
286
								if($this->PutLine(base64_encode($message))==0)
287
									return("Could not send message authentication step message");
288
								$response=$this->GetLine();
289
								if(GetType($response)!="string")
290
									return("Could not get authentication step message response");
291
								switch($this->Tokenize($response," "))
292
								{
293
									case "+OK":
294
										$authenticated=1;
295
										break;
296
									case "+":
297
										$response=base64_decode($this->Tokenize("\r\n"));
298
										break;
299
									default:
300
										return($this->SetError("Authentication error: ".$this->Tokenize("\r\n")));
301
								}
302
								break;
303
							default:
304
								return($this->SetError("Could not process the SASL authentication step: ".$sasl->error));
305
						}
306
					}
307
				}
308
			}
309
			if(!$authenticated)
310
			{
311
				if($this->PutLine("USER $user")==0)
312
					return($this->SetError("Could not send the USER command"));
313
				$response=$this->GetLine();
314
				if(GetType($response)!="string")
315
					return($this->SetError("Could not get user login entry response"));
316
				if($this->Tokenize($response," ")!="+OK")
317
					return($this->SetError("User error: ".$this->Tokenize("\r\n")));
318
				if($this->PutLine("PASS $password")==0)
319
					return($this->SetError("Could not send the PASS command"));
320
				$response=$this->GetLine();
321
				if(GetType($response)!="string")
322
					return($this->SetError("Could not get login password entry response"));
323
				if($this->Tokenize($response," ")!="+OK")
324
					return($this->SetError("Password error: ".$this->Tokenize("\r\n")));
325
			}
326
		}
327
		$this->state="TRANSACTION";
328
		return("");
329
	}
330
331
	/* Statistics method - pass references to variables to hold the number of
332
     messages in the mail box and the size that they take in bytes.  */
333
334
	Function Statistics(&$messages,&$size)
335
	{
336
		if($this->state!="TRANSACTION")
337
			return($this->SetError("connection is not in TRANSACTION state"));
338
		if($this->PutLine("STAT")==0)
339
			return($this->SetError("Could not send the STAT command"));
340
		$response=$this->GetLine();
341
		if(GetType($response)!="string")
342
			return($this->SetError("Could not get the statistics command response"));
343
		if($this->Tokenize($response," ")!="+OK")
344
			return($this->SetError("Could not get the statistics: ".$this->Tokenize("\r\n")));
345
		$messages=$this->Tokenize(" ");
346
		$size=$this->Tokenize(" ");
347
		return("");
348
	}
349
350
	/* ListMessages method - the $message argument indicates the number of a
351
     message to be listed.  If you specify an empty string it will list all
352
     messages in the mail box.  The $unique_id flag indicates if you want
353
     to list the each message unique identifier, otherwise it will
354
     return the size of each message listed.  If you list all messages the
355
     result will be returned in an array. */
356
357
	Function ListMessages($message,$unique_id)
358
	{
359
		if($this->state!="TRANSACTION")
360
			return($this->SetError("connection is not in TRANSACTION state"));
361
		if($unique_id)
362
			$list_command="UIDL";
363
		else
364
			$list_command="LIST";
365
		if($this->PutLine("$list_command".($message ? " ".$message : ""))==0)
366
			return($this->SetError("Could not send the $list_command command"));
367
		$response=$this->GetLine();
368
		if(GetType($response)!="string")
369
			return($this->SetError("Could not get message list command response"));
370
		if($this->Tokenize($response," ")!="+OK")
371
			return($this->SetError("Could not get the message listing: ".$this->Tokenize("\r\n")));
372
		if($message=="")
373
		{
374
			for($messages=array();;)
375
			{
376
				$response=$this->GetLine();
377
				if(GetType($response)!="string")
378
					return($this->SetError("Could not get message list response"));
379
				if($response==".")
380
					break;
381
				$message=intval($this->Tokenize($response," "));
382
				if($unique_id)
383
					$messages[$message]=$this->Tokenize(" ");
384
				else
385
					$messages[$message]=intval($this->Tokenize(" "));
386
			}
387
			return($messages);
388
		}
389
		else
390
		{
391
			$message=intval($this->Tokenize(" "));
392
			$value=$this->Tokenize(" ");
393
			return($unique_id ? $value : intval($value));
394
		}
395
	}
396
397
	/* RetrieveMessage method - the $message argument indicates the number of
398
     a message to be listed.  Pass a reference variables that will hold the
399
     arrays of the $header and $body lines.  The $lines argument tells how
400
     many lines of the message are to be retrieved.  Pass a negative number
401
     if you want to retrieve the whole message. */
402
403
	Function RetrieveMessage($message,&$headers,&$body,$lines)
404
	{
405
		if($this->state!="TRANSACTION")
406
			return($this->SetError("connection is not in TRANSACTION state"));
407
		if($lines<0)
408
		{
409
			$command="RETR";
410
			$arguments="$message";
411
		}
412
		else
413
		{
414
			$command="TOP";
415
			$arguments="$message $lines";
416
		}
417
		if($this->PutLine("$command $arguments")==0)
418
			return($this->SetError("Could not send the $command command"));
419
		$response=$this->GetLine();
420
		if(GetType($response)!="string")
421
			return($this->SetError("Could not get message retrieval command response"));
422
		if($this->Tokenize($response," ")!="+OK")
423
			return($this->SetError("Could not retrieve the message: ".$this->Tokenize("\r\n")));
424
		for($headers=$body=array(),$line=0;;)
425
		{
426
			$response=$this->GetLine();
427
			if(GetType($response)!="string")
428
				return($this->SetError("Could not retrieve the message"));
429
			switch($response)
430
			{
431
				case ".":
432
					return("");
433
				case "":
434
					break 2;
435
				default:
436
					if(substr($response,0,1)==".")
437
						$response=substr($response,1,strlen($response)-1);
438
					break;
439
			}
440
			if($this->join_continuation_header_lines
441
			&& $line>0
442
			&& ($response[0]=="\t"
443
			|| $response[0]==" "))
444
				$headers[$line-1].=$response;
445
			else
446
			{
447
				$headers[$line]=$response;
448
				$line++;
449
			}
450
		}
451
		for($line=0;;$line++)
452
		{
453
			$response=$this->GetLine();
454
			if(GetType($response)!="string")
455
				return($this->SetError("Could not retrieve the message"));
456
			switch($response)
457
			{
458
				case ".":
459
					return("");
460
				default:
461
					if(substr($response,0,1)==".")
462
						$response=substr($response,1,strlen($response)-1);
463
					break;
464
			}
465
			$body[$line]=$response;
466
		}
467
		return("");
468
	}
469
470
	/* DeleteMessage method - the $message argument indicates the number of
471
     a message to be marked as deleted.  Messages will only be effectively
472
     deleted upon a successful call to the Close method. */
473
474
	Function DeleteMessage($message)
475
	{
476
		if($this->state!="TRANSACTION")
477
			return($this->SetError("connection is not in TRANSACTION state"));
478
		if($this->PutLine("DELE $message")==0)
479
			return($this->SetError("Could not send the DELE command"));
480
		$response=$this->GetLine();
481
		if(GetType($response)!="string")
482
			return($this->SetError("Could not get message delete command response"));
483
		if($this->Tokenize($response," ")!="+OK")
484
			return($this->SetError("Could not delete the message: ".$this->Tokenize("\r\n")));
485
		$this->must_update=1;
486
		return("");
487
	}
488
489
	/* ResetDeletedMessages method - Reset the list of marked to be deleted
490
     messages.  No messages will be marked to be deleted upon a successful
491
     call to this method.  */
492
493
	Function ResetDeletedMessages()
494
	{
495
		if($this->state!="TRANSACTION")
496
			return($this->SetError("connection is not in TRANSACTION state"));
497
		if($this->PutLine("RSET")==0)
498
			return($this->SetError("Could not send the RSET command"));
499
		$response=$this->GetLine();
500
		if(GetType($response)!="string")
501
			return($this->SetError("Could not get reset deleted messages command response"));
502
		if($this->Tokenize($response," ")!="+OK")
503
			return($this->SetError("Could not reset deleted messages: ".$this->Tokenize("\r\n")));
504
		$this->must_update=0;
505
		return("");
506
	}
507
508
	/* IssueNOOP method - Just pings the server to prevent it auto-close the
509
     connection after an idle timeout (tipically 10 minutes).  Not very
510
     useful for most likely uses of this class.  It's just here for
511
     protocol support completeness.  */
512
513
	Function IssueNOOP()
514
	{
515
		if($this->state!="TRANSACTION")
516
			return($this->SetError("connection is not in TRANSACTION state"));
517
		if($this->PutLine("NOOP")==0)
518
			return($this->SetError("Could not send the NOOP command"));
519
		$response=$this->GetLine();
520
		if(GetType($response)!="string")
521
			return($this->SetError("Could not NOOP command response"));
522
		if($this->Tokenize($response," ")!="+OK")
523
			return($this->SetError("Could not issue the NOOP command: ".$this->Tokenize("\r\n")));
524
		return("");
525
	}
526
};
527
528
?>