オープンソース・ソフトウェアの開発とダウンロード

Subversion リポジトリの参照

Diff of /trunk/1.8.x/ccs-patch/security/ccsecurity/network.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

trunk/1.7.x/ccs-patch/security/ccsecurity/network.c revision 3502 by kumaneko, Mon Mar 8 08:44:55 2010 UTC branches/ccs-patch/security/ccsecurity/network.c revision 3701 by kumaneko, Fri May 28 12:44:01 2010 UTC
# Line 3  Line 3 
3   *   *
4   * Copyright (C) 2005-2010  NTT DATA CORPORATION   * Copyright (C) 2005-2010  NTT DATA CORPORATION
5   *   *
6   * Version: 1.7.2-pre   2010/03/08   * Version: 1.7.2   2010/04/01
7   *   *
8   * This file is applicable to both 2.4.30 and 2.6.11 and later.   * This file is applicable to both 2.4.30 and 2.6.11 and later.
9   * See README.ccs for ChangeLog.   * See README.ccs for ChangeLog.
# Line 22  Line 22 
22  /**  /**
23   * ccs_audit_network_log - Audit network log.   * ccs_audit_network_log - Audit network log.
24   *   *
25   * @r:          Pointer to "struct ccs_request_info".   * @r: Pointer to "struct ccs_request_info".
  * @operation:  The name of operation.  
  * @address:    An IPv4 or IPv6 address.  
  * @port:       Port number.  
  * @is_granted: True if this is a granted log.  
26   *   *
27   * Returns 0 on success, negative value otherwise.   * Returns 0 on success, negative value otherwise.
28   */   */
29  static int ccs_audit_network_log(struct ccs_request_info *r,  static int ccs_audit_network_log(struct ccs_request_info *r)
30                                   const char *operation, const char *address,  {
31                                   const u16 port, const bool is_granted)          char buf[128];
32  {          const char *operation = ccs_net2keyword(r->param.network.operation);
33          if (!is_granted)          const u32 *address = r->param.network.address;
34                  ccs_warn_log(r, "%s %s %u", operation, address, port);          const u16 port = r->param.network.port;
35          return ccs_write_audit_log(is_granted, r, CCS_KEYWORD_ALLOW_NETWORK          if (r->param.network.is_ipv6)
36                                     "%s %s %u\n", operation, address, port);                  ccs_print_ipv6(buf, sizeof(buf), (const struct in6_addr *)
37                                   address, (const struct in6_addr *) address);
38            else
39                    ccs_print_ipv4(buf, sizeof(buf), r->param.network.ip,
40                                   r->param.network.ip);
41            ccs_write_log(r, CCS_KEYWORD_ALLOW_NETWORK "%s %s %u\n", operation,
42                          buf, port);
43            if (r->granted)
44                    return 0;
45            ccs_warn_log(r, "%s %s %u", operation, buf, port);
46            return ccs_supervisor(r, CCS_KEYWORD_ALLOW_NETWORK "%s %s %u\n",
47                                  operation, buf, port);
48  }  }
49    
50  /**  /**
# Line 47  static int ccs_audit_network_log(struct Line 54  static int ccs_audit_network_log(struct
54   * @min:     Pointer to store min address.   * @min:     Pointer to store min address.
55   * @max:     Pointer to store max address.   * @max:     Pointer to store max address.
56   *   *
57   * Returns 2 if @address is an IPv6, 1 if @address is an IPv4, 0 otherwise.   * Returns CCS_IP_ADDRESS_TYPE_IPv6 if @address is an IPv6,
58     * CCS_IP_ADDRESS_TYPE_IPv4 if @address is an IPv4,
59     * CCS_IP_ADDRESS_TYPE_ADDRESS_GROUP otherwise.
60   */   */
61  int ccs_parse_ip_address(char *address, u16 *min, u16 *max)  int ccs_parse_ip_address(char *address, u16 *min, u16 *max)
62  {  {
# Line 65  int ccs_parse_ip_address(char *address, Line 74  int ccs_parse_ip_address(char *address,
74                          min[i] = htons(min[i]);                          min[i] = htons(min[i]);
75                          max[i] = htons(max[i]);                          max[i] = htons(max[i]);
76                  }                  }
77                  return 2;                  return CCS_IP_ADDRESS_TYPE_IPv6;
78          }          }
79          count = sscanf(address, "%hu.%hu.%hu.%hu-%hu.%hu.%hu.%hu",          count = sscanf(address, "%hu.%hu.%hu.%hu-%hu.%hu.%hu.%hu",
80                         &min[0], &min[1], &min[2], &min[3],                         &min[0], &min[1], &min[2], &min[3],
# Line 75  int ccs_parse_ip_address(char *address, Line 84  int ccs_parse_ip_address(char *address,
84                                 + (((u8) min[2]) << 8) + (u8) min[3]);                                 + (((u8) min[2]) << 8) + (u8) min[3]);
85                  memmove(min, &ip, sizeof(ip));                  memmove(min, &ip, sizeof(ip));
86                  if (count == 8)                  if (count == 8)
87                          ip = htonl((((u8) max[0]) << 24) + (((u8) max[1]) << 16)                          ip = htonl((((u8) max[0]) << 24)
88                                       + (((u8) max[1]) << 16)
89                                     + (((u8) max[2]) << 8) + (u8) max[3]);                                     + (((u8) max[2]) << 8) + (u8) max[3]);
90                  memmove(max, &ip, sizeof(ip));                  memmove(max, &ip, sizeof(ip));
91                  return 1;                  return CCS_IP_ADDRESS_TYPE_IPv4;
92          }          }
93          return 0;          return CCS_IP_ADDRESS_TYPE_ADDRESS_GROUP;
94    }
95    
96    /**
97     * ccs_print_ipv4 - Print an IPv4 address.
98     *
99     * @buffer:     Buffer to write to.
100     * @buffer_len: Size of @buffer.
101     * @min_ip:     Min address in host byte order.
102     * @max_ip:     Max address in host byte order.
103     *
104     * Returns nothing.
105     */
106    void ccs_print_ipv4(char *buffer, const int buffer_len,
107                        const u32 min_ip, const u32 max_ip)
108    {
109            memset(buffer, 0, buffer_len);
110            snprintf(buffer, buffer_len - 1, "%u.%u.%u.%u%c%u.%u.%u.%u",
111                     HIPQUAD(min_ip), min_ip == max_ip ? '\0' : '-',
112                     HIPQUAD(max_ip));
113  }  }
114    
115  #if !defined(NIP6)  #if !defined(NIP6)
# Line 96  int ccs_parse_ip_address(char *address, Line 125  int ccs_parse_ip_address(char *address,
125   *   *
126   * @buffer:     Buffer to write to.   * @buffer:     Buffer to write to.
127   * @buffer_len: Size of @buffer.   * @buffer_len: Size of @buffer.
128   * @ip:         Pointer to "struct in6_addr".   * @min_ip:     Pointer to "struct in6_addr".
129     * @max_ip:     Pointer to "struct in6_addr".
130   *   *
131   * Returns nothing.   * Returns nothing.
132   */   */
133  void ccs_print_ipv6(char *buffer, const int buffer_len,  void ccs_print_ipv6(char *buffer, const int buffer_len,
134                      const struct in6_addr *ip)                      const struct in6_addr *min_ip,
135                        const struct in6_addr *max_ip)
136  {  {
137          memset(buffer, 0, buffer_len);          memset(buffer, 0, buffer_len);
138          snprintf(buffer, buffer_len - 1, "%x:%x:%x:%x:%x:%x:%x:%x", NIP6(*ip));          snprintf(buffer, buffer_len - 1,
139                     "%x:%x:%x:%x:%x:%x:%x:%x%c%x:%x:%x:%x:%x:%x:%x:%x",
140                     NIP6(*min_ip), min_ip == max_ip ? '\0' : '-',
141                     NIP6(*max_ip));
142  }  }
143    
144  /**  /**
# Line 146  const char *ccs_net2keyword(const u8 ope Line 180  const char *ccs_net2keyword(const u8 ope
180          return keyword;          return keyword;
181  }  }
182    
183    static bool ccs_check_network_acl(const struct ccs_request_info *r,
184                                      const struct ccs_acl_info *ptr)
185    {
186            const struct ccs_ip_network_acl *acl =
187                    container_of(ptr, typeof(*acl), head);
188            bool ret;
189            if (!(acl->perm & (1 << r->param.network.operation)) ||
190                !ccs_compare_number_union(r->param.network.port, &acl->port))
191                    return false;
192            switch (acl->address_type) {
193            case CCS_IP_ADDRESS_TYPE_ADDRESS_GROUP:
194                    ret = ccs_address_matches_group(r->param.network.is_ipv6,
195                                                    r->param.network.address,
196                                                    acl->address.group);
197                    break;
198            case CCS_IP_ADDRESS_TYPE_IPv4:
199                    ret = !r->param.network.is_ipv6 &&
200                            acl->address.ipv4.min <= r->param.network.ip &&
201                            r->param.network.ip <= acl->address.ipv4.max;
202                    break;
203            default:
204                    ret = r->param.network.is_ipv6 &&
205                            memcmp(acl->address.ipv6.min, r->param.network.address,
206                                   16) <= 0 &&
207                            memcmp(r->param.network.address, acl->address.ipv6.max,
208                                   16) <= 0;
209                    break;
210            }
211            return ret;
212    }
213    
214  /**  /**
215   * ccs_network_entry2 - Check permission for network operation.   * ccs_network_entry2 - Check permission for network operation.
216   *   *
# Line 162  static int ccs_network_entry2(const bool Line 227  static int ccs_network_entry2(const bool
227                                const u32 *address, const u16 port)                                const u32 *address, const u16 port)
228  {  {
229          struct ccs_request_info r;          struct ccs_request_info r;
         struct ccs_acl_info *ptr;  
         const char *keyword = ccs_net2keyword(operation);  
         const u16 perm = 1 << operation;  
         /* using host byte order to allow u32 comparison than memcmp().*/  
         const u32 ip = ntohl(*address);  
230          int error;          int error;
231          char buf[64];          if (ccs_init_request_info(&r, CCS_MAC_NETWORK_UDP_BIND + operation)
         if (ccs_init_request_info(&r, NULL,  
                                   CCS_MAC_NETWORK_UDP_BIND + operation)  
232              == CCS_CONFIG_DISABLED)              == CCS_CONFIG_DISABLED)
233                  return 0;                  return 0;
234          memset(buf, 0, sizeof(buf));          r.param_type = CCS_TYPE_IP_NETWORK_ACL;
235          if (is_ipv6)          r.param.network.operation = operation;
236                  ccs_print_ipv6(buf, sizeof(buf), (const struct in6_addr *)          r.param.network.is_ipv6 = is_ipv6;
237                                 address);          r.param.network.address = address;
238          else          r.param.network.port = port;
239                  snprintf(buf, sizeof(buf) - 1, "%u.%u.%u.%u", HIPQUAD(ip));          /* using host byte order to allow u32 comparison than memcmp().*/
240            r.param.network.ip = ntohl(*address);
241          do {          do {
242                  error = -EPERM;                  ccs_check_acl(&r, ccs_check_network_acl);
243                  list_for_each_entry_rcu(ptr, &r.domain->acl_info_list, list) {                  error = ccs_audit_network_log(&r);
                         struct ccs_ip_network_acl *acl;  
                         if (ptr->is_deleted ||  
                             ptr->type != CCS_TYPE_IP_NETWORK_ACL)  
                                 continue;  
                         acl = container_of(ptr, struct ccs_ip_network_acl,  
                                            head);  
                         if (!(acl->perm & perm))  
                                 continue;  
                         if (!ccs_compare_number_union(port, &acl->port) ||  
                             !ccs_condition(&r, ptr))  
                                 continue;  
                         switch (acl->address_type) {  
                         case CCS_IP_ADDRESS_TYPE_ADDRESS_GROUP:  
                                 if (!ccs_address_matches_group(is_ipv6,  
                                                                address,  
                                                                acl->address.  
                                                                group))  
                                         continue;  
                                 break;  
                         case CCS_IP_ADDRESS_TYPE_IPv4:  
                                 if (is_ipv6 || ip < acl->address.ipv4.min ||  
                                     acl->address.ipv4.max < ip)  
                                         continue;  
                                 break;  
                         default:  
                                 if (!is_ipv6 ||  
                                     memcmp(acl->address.ipv6.min, address, 16)  
                                     > 0 ||  
                                     memcmp(address, acl->address.ipv6.max, 16)  
                                     > 0)  
                                         continue;  
                                 break;  
                         }  
                         r.cond = ptr->cond;  
                         error = 0;  
                         break;  
                 }  
                 ccs_audit_network_log(&r, keyword, buf, port, !error);  
                 if (!error)  
                         break;  
                 error = ccs_supervisor(&r, CCS_KEYWORD_ALLOW_NETWORK  
                                        "%s %s %u\n", keyword, buf, port);  
244          } while (error == CCS_RETRY_REQUEST);          } while (error == CCS_RETRY_REQUEST);
245          if (r.mode != CCS_CONFIG_ENFORCING)          if (r.mode != CCS_CONFIG_ENFORCING)
246                  error = 0;                  error = 0;
# Line 250  static int ccs_network_entry(const bool Line 267  static int ccs_network_entry(const bool
267          return error;          return error;
268  }  }
269    
270    static bool ccs_same_ip_network_acl(const struct ccs_acl_info *a,
271                                        const struct ccs_acl_info *b)
272    {
273            const struct ccs_ip_network_acl *p1 = container_of(a, typeof(*p1),
274                                                               head);
275            const struct ccs_ip_network_acl *p2 = container_of(b, typeof(*p2),
276                                                               head);
277            return ccs_same_acl_head(&p1->head, &p2->head)
278                    && p1->address_type == p2->address_type &&
279                    p1->address.ipv4.min == p2->address.ipv4.min &&
280                    p1->address.ipv6.min == p2->address.ipv6.min &&
281                    p1->address.ipv4.max == p2->address.ipv4.max &&
282                    p1->address.ipv6.max == p2->address.ipv6.max &&
283                    p1->address.group == p2->address.group &&
284                    ccs_same_number_union(&p1->port, &p2->port);
285    }
286    
287    static bool ccs_merge_ip_network_acl(struct ccs_acl_info *a,
288                                         struct ccs_acl_info *b,
289                                         const bool is_delete)
290    {
291            u8 * const a_perm = &container_of(a, struct ccs_ip_network_acl, head)
292                    ->perm;
293            u8 perm = *a_perm;
294            const u8 b_perm = container_of(b, struct ccs_ip_network_acl, head)
295                    ->perm;
296            if (is_delete)
297                    perm &= ~b_perm;
298            else
299                    perm |= b_perm;
300            *a_perm = perm;
301            return !perm;
302    }
303    
304  /**  /**
305   * ccs_write_network_policy - Write "struct ccs_ip_network_acl" list.   * ccs_write_network - Write "struct ccs_ip_network_acl" list.
306   *   *
307   * @data:      String to parse.   * @data:      String to parse.
308   * @domain:    Pointer to "struct ccs_domain_info".   * @domain:    Pointer to "struct ccs_domain_info".
# Line 260  static int ccs_network_entry(const bool Line 311  static int ccs_network_entry(const bool
311   *   *
312   * Returns 0 on success, negative value otherwise.   * Returns 0 on success, negative value otherwise.
313   */   */
314  int ccs_write_network_policy(char *data, struct ccs_domain_info *domain,  int ccs_write_network(char *data, struct ccs_domain_info *domain,
315                               struct ccs_condition *condition,                        struct ccs_condition *condition, const bool is_delete)
                              const bool is_delete)  
316  {  {
         struct ccs_ip_network_acl *entry = NULL;  
         struct ccs_acl_info *ptr;  
317          struct ccs_ip_network_acl e = {          struct ccs_ip_network_acl e = {
318                  .head.type = CCS_TYPE_IP_NETWORK_ACL,                  .head.type = CCS_TYPE_IP_NETWORK_ACL,
319                  .head.cond = condition,                  .head.cond = condition,
# Line 316  int ccs_write_network_policy(char *data, Line 364  int ccs_write_network_policy(char *data,
364          else          else
365                  return -EINVAL;                  return -EINVAL;
366          switch (ccs_parse_ip_address(w[2], min_address, max_address)) {          switch (ccs_parse_ip_address(w[2], min_address, max_address)) {
367          case 2:          case CCS_IP_ADDRESS_TYPE_IPv6:
368                  e.address_type = CCS_IP_ADDRESS_TYPE_IPv6;                  e.address_type = CCS_IP_ADDRESS_TYPE_IPv6;
369                  e.address.ipv6.min = ccs_get_ipv6_address((struct in6_addr *)                  e.address.ipv6.min = ccs_get_ipv6_address((struct in6_addr *)
370                                                            min_address);                                                            min_address);
# Line 325  int ccs_write_network_policy(char *data, Line 373  int ccs_write_network_policy(char *data,
373                  if (!e.address.ipv6.min || !e.address.ipv6.max)                  if (!e.address.ipv6.min || !e.address.ipv6.max)
374                          goto out;                          goto out;
375                  break;                  break;
376          case 1:          case CCS_IP_ADDRESS_TYPE_IPv4:
377                  e.address_type = CCS_IP_ADDRESS_TYPE_IPv4;                  e.address_type = CCS_IP_ADDRESS_TYPE_IPv4;
378                  /* use host byte order to allow u32 comparison.*/                  /* use host byte order to allow u32 comparison.*/
379                  e.address.ipv4.min = ntohl(*(u32 *) min_address);                  e.address.ipv4.min = ntohl(*(u32 *) min_address);
# Line 335  int ccs_write_network_policy(char *data, Line 383  int ccs_write_network_policy(char *data,
383                  if (w[2][0] != '@')                  if (w[2][0] != '@')
384                          return -EINVAL;                          return -EINVAL;
385                  e.address_type = CCS_IP_ADDRESS_TYPE_ADDRESS_GROUP;                  e.address_type = CCS_IP_ADDRESS_TYPE_ADDRESS_GROUP;
386                  e.address.group = ccs_get_address_group(w[2] + 1);                  e.address.group = ccs_get_group(w[2] + 1, CCS_ADDRESS_GROUP);
387                  if (!e.address.group)                  if (!e.address.group)
388                          return -ENOMEM;                          return -ENOMEM;
389                  break;                  break;
390          }          }
391          if (!ccs_parse_number_union(w[3], &e.port))          if (!ccs_parse_number_union(w[3], &e.port))
392                  goto out;                  goto out;
393          if (!is_delete)          error = ccs_update_domain(&e.head, sizeof(e), is_delete, domain,
394                  entry = kmalloc(sizeof(e), GFP_KERNEL);                                    ccs_same_ip_network_acl,
395          mutex_lock(&ccs_policy_lock);                                    ccs_merge_ip_network_acl);
         list_for_each_entry_rcu(ptr, &domain->acl_info_list, list) {  
                 struct ccs_ip_network_acl *acl =  
                         container_of(ptr, struct ccs_ip_network_acl, head);  
                 if (ptr->type != CCS_TYPE_IP_NETWORK_ACL ||  
                     ptr->cond != condition ||  
                     ccs_memcmp(acl, &e, offsetof(typeof(e), address_type),  
                                sizeof(e)))  
                         continue;  
                 if (is_delete) {  
                         acl->perm &= ~e.perm;  
                         if (!acl->perm)  
                                 ptr->is_deleted = true;  
                 } else {  
                         if (ptr->is_deleted)  
                                 acl->perm = 0;  
                         acl->perm |= e.perm;  
                         ptr->is_deleted = false;  
                 }  
                 error = 0;  
                 break;  
         }  
         if (!is_delete && error && ccs_commit_ok(entry, &e, sizeof(e))) {  
                 ccs_add_domain_acl(domain, &entry->head);  
                 entry = NULL;  
                 error = 0;  
         }  
         mutex_unlock(&ccs_policy_lock);  
396   out:   out:
397          if (w[2][0] == '@')          if (w[2][0] == '@')
398                  ccs_put_address_group(e.address.group);                  ccs_put_group(e.address.group);
399          else if (e.address_type == CCS_IP_ADDRESS_TYPE_IPv6) {          else if (e.address_type == CCS_IP_ADDRESS_TYPE_IPv6) {
400                  ccs_put_ipv6_address(e.address.ipv6.min);                  ccs_put_ipv6_address(e.address.ipv6.min);
401                  ccs_put_ipv6_address(e.address.ipv6.max);                  ccs_put_ipv6_address(e.address.ipv6.max);
402          }          }
403          ccs_put_number_union(&e.port);          ccs_put_number_union(&e.port);
         kfree(entry);  
404          return error;          return error;
405  }  }
406    
# Line 393  int ccs_write_network_policy(char *data, Line 413  int ccs_write_network_policy(char *data,
413   *   *
414   * Returns 0 on success, negative value otherwise.   * Returns 0 on success, negative value otherwise.
415   */   */
416  static inline int ccs_network_listen_acl(const bool is_ipv6, const u8 *address,  static int ccs_network_listen_acl(const bool is_ipv6, const u8 *address,
417                                           const u16 port)                                    const u16 port)
418  {  {
419          return ccs_network_entry(is_ipv6, CCS_NETWORK_TCP_LISTEN,          return ccs_network_entry(is_ipv6, CCS_NETWORK_TCP_LISTEN,
420                                   (const u32 *) address, ntohs(port));                                   (const u32 *) address, ntohs(port));
# Line 410  static inline int ccs_network_listen_acl Line 430  static inline int ccs_network_listen_acl
430   *   *
431   * Returns 0 on success, negative value otherwise.   * Returns 0 on success, negative value otherwise.
432   */   */
433  static inline int ccs_network_connect_acl(const bool is_ipv6,  static int ccs_network_connect_acl(const bool is_ipv6, const int sock_type,
434                                            const int sock_type,                                     const u8 *address, const u16 port)
                                           const u8 *address, const u16 port)  
435  {  {
436          u8 operation;          u8 operation;
437          switch (sock_type) {          switch (sock_type) {
# Line 466  static int ccs_network_bind_acl(const bo Line 485  static int ccs_network_bind_acl(const bo
485   *   *
486   * Returns 0 on success, negative value otherwise.   * Returns 0 on success, negative value otherwise.
487   */   */
488  static inline int ccs_network_accept_acl(const bool is_ipv6, const u8 *address,  static int ccs_network_accept_acl(const bool is_ipv6, const u8 *address,
489                                           const u16 port)                                    const u16 port)
490  {  {
491          int retval;          int retval;
492          current->ccs_flags |= CCS_DONT_SLEEP_ON_ENFORCE_ERROR;          current->ccs_flags |= CCS_DONT_SLEEP_ON_ENFORCE_ERROR;
# Line 487  static inline int ccs_network_accept_acl Line 506  static inline int ccs_network_accept_acl
506   *   *
507   * Returns 0 on success, negative value otherwise.   * Returns 0 on success, negative value otherwise.
508   */   */
509  static inline int ccs_network_sendmsg_acl(const bool is_ipv6,  static int ccs_network_sendmsg_acl(const bool is_ipv6, const int sock_type,
510                                            const int sock_type,                                     const u8 *address, const u16 port)
                                           const u8 *address, const u16 port)  
511  {  {
512          u8 operation;          u8 operation;
513          if (sock_type == SOCK_DGRAM)          if (sock_type == SOCK_DGRAM)
# Line 510  static inline int ccs_network_sendmsg_ac Line 528  static inline int ccs_network_sendmsg_ac
528   *   *
529   * Returns 0 on success, negative value otherwise.   * Returns 0 on success, negative value otherwise.
530   */   */
531  static inline int ccs_network_recvmsg_acl(const bool is_ipv6,  static int ccs_network_recvmsg_acl(const bool is_ipv6, const int sock_type,
532                                            const int sock_type,                                     const u8 *address, const u16 port)
                                           const u8 *address, const u16 port)  
533  {  {
534          int retval;          int retval;
535          const u8 operation          const u8 operation
# Line 525  static inline int ccs_network_recvmsg_ac Line 542  static inline int ccs_network_recvmsg_ac
542          return retval;          return retval;
543  }  }
544    
545    #ifndef CONFIG_NET
546    
547    void __init ccs_network_init(void)
548    {
549    }
550    
551    #else
552    
553  #define MAX_SOCK_ADDR 128 /* net/socket.c */  #define MAX_SOCK_ADDR 128 /* net/socket.c */
554    
555  /* Check permission for creating a socket. */  /* Check permission for creating a socket. */
# Line 938  void __init ccs_network_init(void) Line 963  void __init ccs_network_init(void)
963          ccsecurity_ops.socket_recvmsg_permission =          ccsecurity_ops.socket_recvmsg_permission =
964                  __ccs_socket_recvmsg_permission;                  __ccs_socket_recvmsg_permission;
965  }  }
966    
967    #endif

Legend:
Removed from v.3502  
changed lines
  Added in v.3701

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26