#include <bits/stdc++.h>
using namespace std;
template <typename T>
class Graph {
  void _dfs_toporogical_sort(vector<bool> &visited, const int n,
                             list<long long> &ret) const {
    if (visited[n]) {
      return;
    }
    visited[n] = true;
    for (auto e : this->edge[n]) {
      _dfs_toporogical_sort(visited, e.to, ret);
    }
    ret.push_front(n);
  }

 public:
  struct Edge {
    long long to;
    T distance;
    Edge(const long long t, const T d) {
      this->to = t;
      this->distance = d;
    }
    bool operator<(const Edge &e) const { return this->distance < e.distance; }
    bool operator<=(const Edge &e) const {
      return this->distance <= e.distance;
    }
    bool operator>(const Edge &e) const { return this->distance > e.distance; }
    bool operator>=(const Edge &e) const {
      return this->distance >= e.distance;
    }
    bool operator==(const Edge &e) const {
      return this->distance == e.distance;
    }
  };
  class UnionFind {
   public:
    vector<long long> root;
    UnionFind(const long long N) { this->root = vector<long long>(N + 1, -1); }
    long long find_root(const long long A) {
      if (this->root[A] < 0) {
        return A;
      } else {
        this->root[A] = find_root(this->root[A]);
      }
      return this->root[A];
    }
    long long size(const long long A) { return -this->root[find_root(A)]; }
    bool unite(long long A, long long B) {
      A = find_root(A);
      B = find_root(B);
      if (A == B) {
        return false;
      }
      if (size(A) < size(B)) {
        swap(A, B);
      }
      this->root[A] += this->root[B];
      this->root[B] = A;
      return true;
    }
    bool has_same_root(const long long A, const long long B) {
      return find_root(A) == find_root(B);
    }
  };
  vector<vector<T>> adjacency_matrix;
  long long node;
  T initial_value;
  vector<vector<Edge>> edge;
  Graph(const long long N) {
    set_node(N);
    initialize_edge_vector();
  }
  Graph(const long long N, const T init) {
    set_node(N);
    initialize_edge_vector();
    set_initial_value(init);
  }
  void set_node(const long long N) { this->node = N + 1; }
  void set_initial_value(const long long init) { this->initial_value = init; }
  void initialize_adjacency_matrix() {
    this->adjacency_matrix = vector<vector<T>>(
        this->node, vector<T>(this->node, this->initial_value));
  }
  void initialize_adjacency_matrix(const T init) {
    set_initial_value(init);
    initialize_adjacency_matrix();
  }
  void initialize_edge_vector() {
    this->edge = vector<vector<Edge>>(this->node);
  }
  void update_adjacency_matrix(const long long from, const long long to,
                               const T distance = 1) {
    this->adjacency_matrix[from][to] = distance;
  }
  void add_edge(const long long from, const long long to,
                const T distance = 1) {
    this->edge[from].push_back({to, distance});
  }
  vector<vector<T>> run_floyd_warshall() const {
    vector<vector<T>> ret(this->adjacency_matrix);
    for (long long i = 0; i < this->node; i++) {
      for (long long j = 0; j < this->node; j++) {
        for (long long k = 0; k < this->node; k++) {
          ret[j][k] = min(ret[j][k], ret[j][i] + ret[i][k]);
        }
      }
    }
    for (long long i = 0; i < this->node; i++) {
      for (long long j = 0; j < this->node; j++) {
        for (long long k = 0; k < this->node; k++) {
          ret[j][k] = min(ret[j][k], ret[j][i] + ret[i][k]);
        }
      }
    }
    for (long long i = 0; i < this->node; i++) {
      for (long long j = 0; j < this->node; j++) {
        for (long long k = 0; k < this->node; k++) {
          ret[j][k] = min(ret[j][k], ret[j][i] + ret[i][k]);
        }
      }
    }
    return ret;
  }
  T run_bfs(const long long from, const long long to) const {
    vector<bool> visited(this->node, false);
    queue<long long> q;
    q.push(from);
    for (long long i = 1; i < this->node && !q.empty(); i++) {
      queue<long long> tmp;
      while (!q.empty()) {
        for (Edge e : this->edge[q.front()]) {
          if (e.to == to) {
            return i;
          }
          if (visited[e.to]) {
            continue;
          }
          visited[e.to] = true;
          tmp.push(e.to);
        }
        q.pop();
      }
      q = tmp;
    }
    return this->initial_value;
  }
  vector<T> run_bellman_ford(const long long from) const {
    vector<T> distance(this->node, this->initial_value);
    distance[from] = 0;
    for (long long i = 0; i < this->node - 1; i++) {
      for (long long j = 0; j < this->node; j++) {
        for (Edge e : this->edge[j]) {
          if (distance[e.to] > distance[j] + e.distance) {
            distance[e.to] = distance[j] + e.distance;
          }
        }
      }
    }
    return distance;
  }
  vector<T> run_dijkstra(const long long from) const {
    vector<T> distance(this->node, this->initial_value);
    priority_queue<Edge, vector<Edge>, greater<Edge>> queue;
    queue.push(Edge(from, 0));
    for (long long i = 1; i < this->node && !queue.empty(); i++) {
      T d = queue.top().distance;
      long long to = queue.top().to;
      queue.pop();
      if (distance[to] == this->initial_value) {
        distance[to] = d;
        for (Edge e : this->edge[to]) {
          if (distance[e.to] == this->initial_value) {
            queue.push({e.to, e.distance + d});
          }
        }
      } else {
        i--;
      }
    }
    return distance;
  }
  T run_prim() const {
    vector<bool> is_visited(this->node, false);
    T cost = 0;
    priority_queue<Edge, vector<Edge>, greater<Edge>> queue;
    queue.push(Edge(1, 0));
    for (long long i = 1; i < this->node && !queue.empty(); i++) {
      T distance = queue.top().distance;
      long long to = queue.top().to;
      queue.pop();
      if (is_visited[to]) {
        i--;
      } else {
        cost += distance;
        is_visited[to] = true;
        for (Edge e : this->edge[to]) {
          queue.push({e.to, e.distance});
        }
      }
    }
    return cost;
  }
  T run_kruskal() const {
    T cost = 0;
    vector<pair<Edge, long long>> edge_from;
    for (long long i = 0; i < this->node; i++) {
      for (auto e : this->edge[i]) {
        edge_from.push_back(make_pair(e, i));
      }
    }
    sort(edge_from.begin(), edge_from.end());
    UnionFind uf(this->node);
    for (pair<Edge, long long> e_f : edge_from) {
      auto e = e_f.first;
      long long from = e_f.second;
      if (!uf.has_same_root(from, e.to)) {
        uf.unite(from, e.to);
        cost += e.distance;
      }
    }
    return cost;
  }
  vector<long long> run_toporogical_sort() const {
    list<long long> ret_list;
    vector<bool> visited(this->node, false);
    for (long long i = 0; i < this->node; i++) {
      _dfs_toporogical_sort(visited, i, ret_list);
    }
    vector<long long> ret(ret_list.begin(), ret_list.end());
    return ret;
  }
};
int main() {
  int H, W;
  cin >> H >> W;
  vector<vector<bool>> field(H, vector<bool>(W));
  string S;
  for (int i = 0; i < H; i++) {
    cin >> S;
    for (int j = 0; j < W; j++) {
      field[i][j] = (S[j] == '.');
    }
  }
  Graph<long long> g(H * W, 1e7);
  g.initialize_adjacency_matrix();
  vector<pair<int, int>> diff{pair<int, int>(1, 0), pair<int, int>(0, 1),
                              pair<int, int>(-1, 0), pair<int, int>(0, -1)};
  for (int i = 0; i < H; i++) {
    for (int j = 0; j < W; j++) {
      for (pair<int, int> d : diff) {
        int new_i = i + d.first;
        int new_j = j + d.second;
        if (!(0 <= new_i && new_i < H)) {
          continue;
        }
        if (!(0 <= new_j && new_j < W)) {
          continue;
        }
        if (field[i][j] && field[new_i][new_j]) {
          g.update_adjacency_matrix(W * i + j, W * new_i + new_j, 1);
          g.update_adjacency_matrix(W * new_i + new_j, W * i + j, 1);
        }
      }
    }
  }
  long long ans = 1;
  vector<vector<long long>> distance = g.run_floyd_warshall();
  for (int i = 0; i < H; i++) {
    for (int j = 0; j < W; j++) {
      if (!field[i][j]) {
        continue;
      }
      for (int ii = 0; ii < H; ii++) {
        for (int jj = 0; jj < W; jj++) {
          if (!field[ii][jj]) {
            continue;
          }
          ans = max(ans, distance[W * i + j][W * ii + jj]);
        }
      }
    }
  }
  cout << ans << endl;
}
