1 /*
2  * snmpclnt.cxx
3  *
4  * SNMP Client class
5  *
6  * Portable Windows Library
7  *
8  * Copyright (c) 1993-2002 Equivalence Pty. Ltd.
9  *
10  * The contents of this file are subject to the Mozilla Public License
11  * Version 1.0 (the "License"); you may not use this file except in
12  * compliance with the License. You may obtain a copy of the License at
13  * http://www.mozilla.org/MPL/
14  *
15  * Software distributed under the License is distributed on an "AS IS"
16  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17  * the License for the specific language governing rights and limitations
18  * under the License.
19  *
20  * The Original Code is Portable Windows Library.
21  *
22  * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
23  *
24  * Contributor(s): ______________________________________.
25  *
26  * $Revision: 20385 $
27  * $Author: rjongbloed $
28  * $Date: 2008-06-04 05:40:38 -0500 (Wed, 04 Jun 2008) $
29  */
30 
31 #include <ptlib.h>
32 #include <ptbuildopts.h>
33 
34 #ifdef P_SNMP
35 
36 #include <ptclib/psnmp.h>
37 
38 #define new PNEW
39 
40 
41 #define SNMP_VERSION 0
42 #define SNMP_PORT    "snmp 161"
43 
44 static const char defaultCommunity[] = "public";
45 
46 
47 ///////////////////////////////////////////////////////////////
48 //
49 //  PSNMPClient
50 //
51 
PSNMPClient(PINDEX retry,PINDEX timeout,PINDEX rxSize,PINDEX txSize)52 PSNMPClient::PSNMPClient(PINDEX retry, PINDEX timeout,
53                          PINDEX rxSize, PINDEX txSize)
54  : community(defaultCommunity),
55    version(SNMP_VERSION),
56    retryMax(retry),
57    maxRxSize(rxSize),
58    maxTxSize(txSize)
59 {
60   SetReadTimeout(PTimeInterval(0, timeout));
61   requestId = rand() % 0x7fffffff;
62 }
63 
64 
PSNMPClient(const PString & host,PINDEX retry,PINDEX timeout,PINDEX rxSize,PINDEX txSize)65 PSNMPClient::PSNMPClient(const PString & host, PINDEX retry,
66                          PINDEX timeout, PINDEX rxSize, PINDEX txSize)
67  : hostName(host),
68    community(defaultCommunity),
69    version(SNMP_VERSION),
70    retryMax(retry),
71    maxRxSize(rxSize),
72    maxTxSize(txSize)
73 {
74   SetReadTimeout(PTimeInterval(0, timeout));
75   Open(new PUDPSocket(host, SNMP_PORT));
76   requestId = rand() % 0x7fffffff;
77 }
78 
79 
SetVersion(PASNInt newVersion)80 void PSNMPClient::SetVersion(PASNInt newVersion)
81 {
82   version = newVersion;
83 }
84 
85 
GetVersion() const86 PASNInt PSNMPClient::GetVersion() const
87 {
88   return version;
89 }
90 
91 
SetCommunity(const PString & str)92 void PSNMPClient::SetCommunity(const PString & str)
93 {
94   community = str;
95 }
96 
97 
GetCommunity() const98 PString PSNMPClient::GetCommunity() const
99 {
100   return community;
101 }
102 
103 
SetRequestID(PASNInt newRequestID)104 void PSNMPClient::SetRequestID(PASNInt newRequestID)
105 {
106   requestId = newRequestID;
107 }
108 
109 
GetRequestID() const110 PASNInt PSNMPClient::GetRequestID() const
111 {
112   return requestId;
113 }
114 
115 
WriteGetRequest(PSNMPVarBindingList & varsIn,PSNMPVarBindingList & varsOut)116 PBoolean PSNMPClient::WriteGetRequest(PSNMPVarBindingList & varsIn,
117                                   PSNMPVarBindingList & varsOut)
118 {
119   return WriteRequest(GetRequest, varsIn, varsOut);
120 }
121 
122 
WriteGetNextRequest(PSNMPVarBindingList & varsIn,PSNMPVarBindingList & varsOut)123 PBoolean PSNMPClient::WriteGetNextRequest(PSNMPVarBindingList & varsIn,
124                                       PSNMPVarBindingList & varsOut)
125 {
126   return WriteRequest(GetNextRequest, varsIn, varsOut);
127 }
128 
129 
WriteSetRequest(PSNMPVarBindingList & varsIn,PSNMPVarBindingList & varsOut)130 PBoolean PSNMPClient::WriteSetRequest(PSNMPVarBindingList & varsIn,
131                                   PSNMPVarBindingList & varsOut)
132 {
133   return WriteRequest(SetRequest, varsIn, varsOut);
134 }
135 
136 
GetLastErrorCode() const137 PSNMP::ErrorType PSNMPClient::GetLastErrorCode() const
138 {
139   return lastErrorCode;
140 }
141 
142 
GetLastErrorIndex() const143 PINDEX PSNMPClient::GetLastErrorIndex() const
144 {
145   return lastErrorIndex;
146 }
147 
148 
GetLastErrorText() const149 PString PSNMPClient::GetLastErrorText() const
150 {
151   return PSNMP::GetErrorText(lastErrorCode);
152 }
153 
ReadRequest(PBYTEArray & readBuffer)154 PBoolean PSNMPClient::ReadRequest(PBYTEArray & readBuffer)
155 {
156   readBuffer.SetSize(maxRxSize);
157   PINDEX rxSize = 0;
158 
159   for (;;) {
160 
161     if (!Read(readBuffer.GetPointer()+rxSize, maxRxSize - rxSize)) {
162 
163       // if the buffer was too small, then we are receiving datagrams
164       // and the datagram was too big
165       if (PChannel::GetErrorCode() == PChannel::BufferTooSmall)
166         lastErrorCode = RxBufferTooSmall;
167       else
168         lastErrorCode = NoResponse;
169       return PFalse;
170 
171     } else if ((rxSize + GetLastReadCount()) >= 10)
172       break;
173 
174     else
175       rxSize += GetLastReadCount();
176   }
177 
178   rxSize += GetLastReadCount();
179 
180   PINDEX hdrLen = 1;
181 
182   // if not a valid sequence header, then stop reading
183   WORD len;
184   if ((readBuffer[0] != 0x30) ||
185       !PASNObject::DecodeASNLength(readBuffer, hdrLen, len)) {
186     lastErrorCode = MalformedResponse;
187     return PFalse;
188   }
189 
190   // length of packet is length of header + length of data
191   len = (WORD)(len + hdrLen);
192 
193   // return PTrue if we have the packet, else return PFalse
194   if (len <= maxRxSize)
195     return PTrue;
196 
197   lastErrorCode = RxBufferTooSmall;
198   return PFalse;
199 
200 #if 0
201   // and get a new data ptr
202   if (maxRxSize < len)
203     readBuffer.SetSize(len);
204 
205   // read the remainder of the packet
206   while (rxSize < len) {
207     if (!Read(readBuffer.GetPointer()+rxSize, len - rxSize)) {
208       lastErrorCode = NoResponse;
209       return PFalse;
210     }
211     rxSize += GetLastReadCount();
212   }
213   return PTrue;
214 #endif
215 }
216 
WriteRequest(PASNInt requestCode,PSNMPVarBindingList & vars,PSNMPVarBindingList & varsOut)217 PBoolean PSNMPClient::WriteRequest(PASNInt requestCode,
218                                PSNMPVarBindingList & vars,
219                                PSNMPVarBindingList & varsOut)
220 {
221   PASNSequence pdu;
222   PASNSequence * pduData     = new PASNSequence((BYTE)requestCode);
223   PASNSequence * bindingList = new PASNSequence();
224 
225   lastErrorIndex = 0;
226 
227   // build a get request PDU
228   pdu.AppendInteger(version);
229   pdu.AppendString(community);
230   pdu.Append(pduData);
231 
232   // build the PDU data
233   PASNInt thisRequestId = requestId;
234   requestId = rand() % 0x7fffffff;
235   pduData->AppendInteger(thisRequestId);
236   pduData->AppendInteger(0);           // error status
237   pduData->AppendInteger(0);           // error index
238   pduData->Append(bindingList);        // binding list
239 
240   // build the binding list
241   PINDEX i;
242   for (i = 0; i < vars.GetSize(); i++) {
243     PASNSequence * binding = new PASNSequence();
244     bindingList->Append(binding);
245     binding->AppendObjectID(vars.GetObjectID(i));
246     binding->Append((PASNObject *)vars[i].Clone());
247   }
248 
249   // encode the PDU into a buffer
250   PBYTEArray sendBuffer;
251   pdu.Encode(sendBuffer);
252 
253   if (sendBuffer.GetSize() > maxTxSize) {
254     lastErrorCode = TxDataTooBig;
255     return PFalse;
256   }
257 
258   varsOut.RemoveAll();
259 
260   PINDEX retry = retryMax;
261 
262   for (;;) {
263 
264     // send the packet
265     if (!Write(sendBuffer, sendBuffer.GetSize())) {
266       lastErrorCode = SendFailed;
267       return PFalse;
268     }
269 
270     // receive a packet
271     if (ReadRequest(readBuffer))
272       break;
273     else if ((lastErrorCode != NoResponse) || (retry == 0))
274       return PFalse;
275     else
276       retry--;
277   }
278 
279   // parse the response
280   PASNSequence response(readBuffer);
281   PINDEX seqLen = response.GetSize();
282 
283   // check PDU
284   if (seqLen != 3 ||
285       response[0].GetType() != PASNObject::Integer ||
286       response[1].GetType() != PASNObject::String ||
287       response[2].GetType() != PASNObject::Choice) {
288     lastErrorCode = MalformedResponse;
289     return PFalse;
290   }
291 
292   // check the PDU data
293   const PASNSequence & rPduData = response[2].GetSequence();
294   seqLen = rPduData.GetSize();
295   if (seqLen != 4 ||
296       rPduData.GetChoice()  != GetResponse ||
297       rPduData[0].GetType() != PASNObject::Integer ||
298       rPduData[1].GetType() != PASNObject::Integer ||
299       rPduData[2].GetType() != PASNObject::Integer ||
300       rPduData[3].GetType() != PASNObject::Sequence) {
301     lastErrorCode = MalformedResponse;
302     return PFalse;
303   }
304 
305   // check the request ID
306   PASNInt returnedRequestId = rPduData[0].GetInteger();
307   if (returnedRequestId != thisRequestId) {
308     lastErrorCode = MalformedResponse;
309     return PFalse;
310   }
311 
312   // check the error status and return if non-zero
313   PASNInt errorStatus = rPduData[1].GetInteger();
314   if (errorStatus != 0) {
315     lastErrorIndex = rPduData[2].GetInteger();
316     lastErrorCode = (ErrorType)errorStatus;
317     return PFalse;
318   }
319 
320   // check the variable bindings
321   const PASNSequence & rBindings = rPduData[3].GetSequence();
322   PINDEX bindingCount = rBindings.GetSize();
323 
324   // create the return list
325   for (i = 0; i < bindingCount; i++) {
326     if (rBindings[i].GetType() != PASNObject::Sequence) {
327       lastErrorIndex = i+1;
328       lastErrorCode  = MalformedResponse;
329       return PFalse;
330     }
331     const PASNSequence & rVar = rBindings[i].GetSequence();
332     if (rVar.GetSize() != 2 ||
333         rVar[0].GetType() != PASNObject::ObjectID) {
334       lastErrorIndex = i+1;
335       lastErrorCode = MalformedResponse;
336       return PFalse;
337     }
338     varsOut.Append(rVar[0].GetString(), (PASNObject *)rVar[1].Clone());
339   }
340 
341   lastErrorCode = NoError;
342   return PTrue;
343 }
344 
345 
346 
347 ///////////////////////////////////////////////////////////////
348 //
349 //  Trap routines
350 //
351 
SendEnterpriseTrap(const PIPSocket::Address & addr,const PString & community,const PString & enterprise,PINDEX specificTrap,PASNUnsigned timeTicks,WORD sendPort)352 void PSNMP::SendEnterpriseTrap (
353                  const PIPSocket::Address & addr,
354                             const PString & community,
355                             const PString & enterprise,
356                                      PINDEX specificTrap,
357                                PASNUnsigned timeTicks,
358                                        WORD sendPort)
359 {
360   PSNMPVarBindingList vars;
361   SendTrap(addr,
362            EnterpriseSpecific,
363            community,
364            enterprise,
365            specificTrap,
366            timeTicks,
367            vars,
368            sendPort);
369 }
370 
371 
SendEnterpriseTrap(const PIPSocket::Address & addr,const PString & community,const PString & enterprise,PINDEX specificTrap,PASNUnsigned timeTicks,const PSNMPVarBindingList & vars,WORD sendPort)372 void PSNMP::SendEnterpriseTrap (
373                  const PIPSocket::Address & addr,
374                             const PString & community,
375                             const PString & enterprise,
376                                      PINDEX specificTrap,
377                                PASNUnsigned timeTicks,
378                 const PSNMPVarBindingList & vars,
379                                        WORD sendPort)
380 {
381   SendTrap(addr,
382            EnterpriseSpecific,
383            community,
384            enterprise,
385            specificTrap,
386            timeTicks,
387            vars,
388            sendPort);
389 }
390 
391 
SendTrap(const PIPSocket::Address & addr,PSNMP::TrapType trapType,const PString & community,const PString & enterprise,PINDEX specificTrap,PASNUnsigned timeTicks,const PSNMPVarBindingList & vars,WORD sendPort)392 void PSNMP::SendTrap(const PIPSocket::Address & addr,
393                                 PSNMP::TrapType trapType,
394                                 const PString & community,
395                                 const PString & enterprise,
396                                          PINDEX specificTrap,
397                                    PASNUnsigned timeTicks,
398                     const PSNMPVarBindingList & vars,
399                                            WORD sendPort)
400 {
401   PIPSocket::Address agentAddress;
402   PIPSocket::GetHostAddress(agentAddress);
403   SendTrap(addr,
404            trapType,
405            community,
406            enterprise,
407            specificTrap,
408            timeTicks,
409            vars,
410            agentAddress,
411            sendPort);
412 }
413 
414 
SendTrap(const PIPSocket::Address & addr,PSNMP::TrapType trapType,const PString & community,const PString & enterprise,PINDEX specificTrap,PASNUnsigned timeTicks,const PSNMPVarBindingList & vars,const PIPSocket::Address & agentAddress,WORD sendPort)415 void PSNMP::SendTrap(const PIPSocket::Address & addr,
416                                 PSNMP::TrapType trapType,
417                                 const PString & community,
418                                 const PString & enterprise,
419                                          PINDEX specificTrap,
420                                    PASNUnsigned timeTicks,
421                     const PSNMPVarBindingList & vars,
422                      const PIPSocket::Address & agentAddress,
423                                            WORD sendPort)
424 
425 {
426   // send the trap to specified remote host
427   PUDPSocket socket(addr, sendPort);
428   if (socket.IsOpen())
429     WriteTrap(socket, trapType, community, enterprise,
430               specificTrap, timeTicks, vars, agentAddress);
431 }
432 
WriteTrap(PChannel & channel,PSNMP::TrapType trapType,const PString & community,const PString & enterprise,PINDEX specificTrap,PASNUnsigned timeTicks,const PSNMPVarBindingList & vars,const PIPSocket::Address & agentAddress)433 void PSNMP::WriteTrap(                 PChannel & channel,
434                                   PSNMP::TrapType trapType,
435                                   const PString & community,
436                                   const PString & enterprise,
437                                            PINDEX specificTrap,
438                                      PASNUnsigned timeTicks,
439                       const PSNMPVarBindingList & vars,
440                        const PIPSocket::Address & agentAddress)
441 {
442   PASNSequence pdu;
443   PASNSequence * pduData     = new PASNSequence((BYTE)Trap);
444   PASNSequence * bindingList = new PASNSequence();
445 
446   // build a trap PDU PDU
447   pdu.AppendInteger(1);
448   pdu.AppendString(community);
449   pdu.Append(pduData);
450 
451   // build the PDU data
452   pduData->AppendObjectID(enterprise);               // enterprise
453   pduData->Append(new PASNIPAddress(agentAddress)); // agent address
454   pduData->AppendInteger((int)trapType);             // trap type
455   pduData->AppendInteger(specificTrap);              // specific trap
456   pduData->Append(new PASNTimeTicks(timeTicks));    // time of event
457   pduData->Append(bindingList);                      // binding list
458 
459   // build the binding list
460   for (PINDEX i = 0; i < vars.GetSize(); i++) {
461     PASNSequence * binding = new PASNSequence();
462     bindingList->Append(binding);
463     binding->AppendObjectID(vars.GetObjectID(i));
464     binding->Append((PASNObject *)vars[i].Clone());
465   }
466 
467   // encode the PDU into a buffer
468   PBYTEArray sendBuffer;
469   pdu.Encode(sendBuffer);
470 
471   // send the trap to specified remote host
472   channel.Write(sendBuffer, sendBuffer.GetSize());
473 }
474 
475 #endif // P_SNMP
476 
477 // End Of File ///////////////////////////////////////////////////////////////
478